[Checkins] SVN: z3c.dobbin/trunk/ Implemented basic polymorphic relations; added initial developer documentation.

Malthe Borch mborch at gmail.com
Thu Jun 19 20:57:47 EDT 2008


Log message for revision 87582:
  Implemented basic polymorphic relations; added initial developer documentation.

Changed:
  U   z3c.dobbin/trunk/CHANGES.txt
  A   z3c.dobbin/trunk/docs/
  A   z3c.dobbin/trunk/docs/DEVELOPER.txt
  U   z3c.dobbin/trunk/setup.py
  U   z3c.dobbin/trunk/src/z3c/dobbin/README.txt
  U   z3c.dobbin/trunk/src/z3c/dobbin/interfaces.py
  U   z3c.dobbin/trunk/src/z3c/dobbin/relations.py
  U   z3c.dobbin/trunk/src/z3c/dobbin/session.py
  U   z3c.dobbin/trunk/src/z3c/dobbin/soup.py

-=-
Modified: z3c.dobbin/trunk/CHANGES.txt
===================================================================
--- z3c.dobbin/trunk/CHANGES.txt	2008-06-20 00:57:00 UTC (rev 87581)
+++ z3c.dobbin/trunk/CHANGES.txt	2008-06-20 00:57:45 UTC (rev 87582)
@@ -1,10 +1,12 @@
-=======
-CHANGES
-=======
+Change log
+==========
 
 0.3 dev
 -------
 
+- Implemented polymorphic relations for a subset of the basic types
+  (int, str, unicode, tuple and list).
+
 0.2.9
 -----
 

Added: z3c.dobbin/trunk/docs/DEVELOPER.txt
===================================================================
--- z3c.dobbin/trunk/docs/DEVELOPER.txt	                        (rev 0)
+++ z3c.dobbin/trunk/docs/DEVELOPER.txt	2008-06-20 00:57:45 UTC (rev 87582)
@@ -0,0 +1,42 @@
+Developer information
+=====================
+
+This section details the object persistence model.
+
+Introduction
+------------
+
+Objects that need persisting are required to declare their attributes
+in an interface. Attributes that are not declared are considered
+volatile.
+
+Concrete attributes may be strongly typed using the schema fields that
+correspond to their type; polymorphic attributes are available using
+relational properties.
+
+Relations
+---------
+
+There are two kinds of objects that can be related: instances and
+rocks. Relations are polymorphic such that they support both kinds.
+
+An attribute can hold a single relation or many, using one of the
+built-in sequence types: list, tuple, set, dict.
+
+The following fields allow polymorphic relations of any kind:
+
+  * zope.schema.Object
+  * zope.interface.Attribute
+
+Additional structure can be declared using the sequence fields:
+
+  * zope.schema.List
+  * zope.schema.Dict
+  * zope.schema.Set
+
+When translated to column in a table, all relations are soup object
+references; the soup specification will reflect the type.
+
+Essentially, all polymorphic relations are many-to-many from a
+database perspective. 
+

Modified: z3c.dobbin/trunk/setup.py
===================================================================
--- z3c.dobbin/trunk/setup.py	2008-06-20 00:57:00 UTC (rev 87581)
+++ z3c.dobbin/trunk/setup.py	2008-06-20 00:57:45 UTC (rev 87582)
@@ -14,14 +14,17 @@
 
 from setuptools import setup, find_packages
 
+def read(*files):
+    return "\n".join((open(f).read() for f in files))
+
 setup(name='z3c.dobbin',
-      version='0.2.9',
+      version='0.3dev',
       license='ZPL',
       author = "Malthe Borch, Stefan Eletzhofer and the Zope Community",
       author_email = "zope-dev at zope.org",
       description="Relational object persistance framework",
-      long_description=open('README.txt').read()+open('src/z3c/dobbin/README.txt').read(),
-      keywords='',
+      long_description=read('README.txt', 'docs/DEVELOPER.txt', 'src/z3c/dobbin/README.txt'),
+      keywords='zope orm persistence',
       classifiers=['Programming Language :: Python',
                    'Environment :: Web Environment',
                    'Framework :: Zope3',

Modified: z3c.dobbin/trunk/src/z3c/dobbin/README.txt
===================================================================
--- z3c.dobbin/trunk/src/z3c/dobbin/README.txt	2008-06-20 00:57:00 UTC (rev 87581)
+++ z3c.dobbin/trunk/src/z3c/dobbin/README.txt	2008-06-20 00:57:45 UTC (rev 87582)
@@ -243,11 +243,10 @@
     >>> favorite.item is cleaner
     True
 
-Internally, this is done by setting an attribute on the original
-object that points to the database item, and maintaining a list of
-pending objects on the current database session:
+The session keeps a copy of the pending object until the transaction
+is ended.
 
-    >>> cleaner._d_uuid in session._d_pending
+    >>> cleaner in session._d_pending.values()
     True
 
 However, once we commit the transaction, the relation is no longer
@@ -265,6 +264,46 @@
 This behavior should work well in a request-response type environment,
 where the request will typically end with a commit.
 
+Polymorphic relations
+---------------------
+
+We can create relations to instances as well as immutable objects
+(rocks).
+
+Integers, floats and unicode strings are straight-forward.
+
+    >>> favorite.item = 42; transaction.commit()
+    >>> favorite.item
+    42
+
+    >>> favorite.item = 42.01; transaction.commit()
+    >>> 42 < favorite.item <= 42.01
+    True
+
+    >>> favorite.item = u"My favorite number is 42."; transaction.commit()
+    >>> favorite.item
+    u'My favorite number is 42.'
+
+Normal strings need explicit coercing to ``str``.
+    
+    >>> favorite.item = "My favorite number is 42."; transaction.commit()
+    >>> str(favorite.item)
+    'My favorite number is 42.'
+
+Or sequences of relations.
+
+    >>> favorite.item = (u"green", u"blue", u"red"); transaction.commit()
+    >>> favorite.item
+    (u'green', u'blue', u'red')
+
+When we create relations to mutable objects, a hook is made into the
+transaction machinery to keep track of the pending state.
+
+    >>> some_list = [u"green", u"blue", u"red"]; transaction.commit()
+    >>> favorite.item = some_list
+    >>> favorite.item
+    [u'green', u'blue', u'red']
+    
 Collections
 -----------
 
@@ -352,8 +391,25 @@
 For good measure, let's create a new instance without adding any
 elements to its list.
 
-    >>> _ = create(ICollection)
+    >>> empty_collection = create(ICollection)
+    >>> session.save(empty_collection)
+    
+Let's index the collection by artist in a catalog.
+    
+    >>> class ICatalog(interface.Interface):
+    ...     records_by_artist = schema.Dict(
+    ...         title=u"Records by artist",
+    ...         value_type=schema.List())
+    ...
+    ...     artist_biographies = schema.Dict(
+    ...         title=u"Artist biographies",
+    ...         value_type=schema.Text())
 
+    >> catalog = create(ICatalog)
+    >> session.add(catalog)
+
+    >> session.records_by_artist[diana.artist] = 
+    
 Security
 --------
 

Modified: z3c.dobbin/trunk/src/z3c/dobbin/interfaces.py
===================================================================
--- z3c.dobbin/trunk/src/z3c/dobbin/interfaces.py	2008-06-20 00:57:00 UTC (rev 87581)
+++ z3c.dobbin/trunk/src/z3c/dobbin/interfaces.py	2008-06-20 00:57:45 UTC (rev 87582)
@@ -1,4 +1,5 @@
 from zope import interface
+from zope import schema
 
 class IMapped(interface.Interface):    
     __mapper__ = interface.Attribute(
@@ -6,3 +7,31 @@
 
 class IMapper(interface.Interface):
     """An ORM mapper for a particular specification."""
+
+class IBasicType(interface.Interface):
+    """A basic Python value type."""
+    
+class IIntegerBasicType(IBasicType):
+    value = schema.Int()
+
+class IFloatBasicType(IBasicType):
+    value = schema.Float()
+    
+class IUnicodeBasicType(IBasicType):
+    value = schema.Text()
+    
+class IStringBasicType(IBasicType):
+    value = schema.Bytes()
+
+class ITupleBasicType(IBasicType):
+    value = schema.Tuple()
+
+class IListBasicType(IBasicType):
+    value = schema.List()
+
+class ISetBasicType(IBasicType):
+    value = schema.Set()
+
+class IDictBasicType(IBasicType):
+    value = schema.Dict()
+    

Modified: z3c.dobbin/trunk/src/z3c/dobbin/relations.py
===================================================================
--- z3c.dobbin/trunk/src/z3c/dobbin/relations.py	2008-06-20 00:57:00 UTC (rev 87581)
+++ z3c.dobbin/trunk/src/z3c/dobbin/relations.py	2008-06-20 00:57:45 UTC (rev 87582)
@@ -30,6 +30,7 @@
     target = property(_get_target, _set_target)
     
 class RelationProperty(property):
+    
     def __init__(self, field):
         self.field = field
         self.name = field.__name__+'_relation'
@@ -37,12 +38,17 @@
 
     def get(kls, instance):
         item = getattr(instance, kls.name)
-        return soup.lookup(item.uuid)
+        obj = soup.lookup(item.uuid)
 
+        if interfaces.IBasicType.providedBy(obj):
+            return obj.value
+        else:
+            return obj
+
     def set(kls, instance, item):
         if not interfaces.IMapped.providedBy(item):
             item = soup.persist(item)
-
+        
         if item.id is None:
             session = Session()
             session.save(item)

Modified: z3c.dobbin/trunk/src/z3c/dobbin/session.py
===================================================================
--- z3c.dobbin/trunk/src/z3c/dobbin/session.py	2008-06-20 00:57:00 UTC (rev 87581)
+++ z3c.dobbin/trunk/src/z3c/dobbin/session.py	2008-06-20 00:57:45 UTC (rev 87582)
@@ -21,17 +21,18 @@
     
     interface.implements(ISavepointDataManager)
 
-    def __init__(self, obj):
+    def __init__(self, obj, uuid):
         self.registered = False
         self.vote = False
         self.obj = obj
-
+        self.uuid = uuid
+        
         session = Session()
 
         try:
-            session._d_pending[obj._d_uuid] = obj
+            session._d_pending[uuid] = obj
         except AttributeError:
-            session._d_pending = {obj._d_uuid: obj}
+            session._d_pending = {uuid: obj}
         
     def register(self):
         if not self.registered:
@@ -46,7 +47,7 @@
 
     def commit(self, transaction):
         obj = self.obj
-        uuid = obj._d_uuid
+        uuid = self.uuid
 
         # unset pending state
         session = Session()
@@ -67,7 +68,7 @@
     def tpc_abort(self, transaction):
         # unset pending state
         session = Session()
-        del session._d_pending[uuid]
+        del session._d_pending[self.uuid]
         
         self.registered = False
 
@@ -76,5 +77,14 @@
     def sortKey(self):
         return id(self)
 
-def getTransactionManager(obj):
-    return TransactionManager(obj)
+def registerObject(obj, uuid):
+    session = Session()
+
+    try:
+        pending = session._d_pending.keys()
+    except AttributeError:
+        pending = ()
+    
+    if obj not in pending:
+        TransactionManager(obj, uuid).register()
+

Modified: z3c.dobbin/trunk/src/z3c/dobbin/soup.py
===================================================================
--- z3c.dobbin/trunk/src/z3c/dobbin/soup.py	2008-06-20 00:57:00 UTC (rev 87581)
+++ z3c.dobbin/trunk/src/z3c/dobbin/soup.py	2008-06-20 00:57:45 UTC (rev 87582)
@@ -3,29 +3,45 @@
 from interfaces import IMapper
 from interfaces import IMapped
 
-from session import getTransactionManager
+from session import registerObject
 
 from zope.dottedname.resolve import resolve
 from ore.alchemist import Session
 
 import factory
 import bootstrap
+import interfaces
+import types
 
-def lookup(uuid, ignore_cache=False):
+BASIC_TYPES = (int, float, str, unicode, tuple, list, set, dict)
+
+IMMUTABLE_TYPES = (int, float, str, unicode, tuple)
+
+FACTORY_TYPE_MAP = {
+    types.IntType: interfaces.IIntegerBasicType,
+    types.FloatType: interfaces.IFloatBasicType,
+    types.UnicodeType: interfaces.IUnicodeBasicType,
+    types.StringType: interfaces.IStringBasicType,
+    types.TupleType: interfaces.ITupleBasicType,
+    types.ListType: interfaces.IListBasicType,
+    type(set()): interfaces.ISetBasicType,
+    types.DictType: interfaces.IDictBasicType}
+    
+def lookup(uuid, ignore_pending=False):
     session = Session()
 
+    # check if object is in pending session objects
+    if not ignore_pending:
+        try:
+            return session._d_pending[uuid]
+        except (AttributeError, KeyError):
+            pass
+
     try:
         item = session.query(bootstrap.Soup).filter_by(uuid=uuid)[0]
     except IndexError:
         raise LookupError("Unable to locate object with UUID = '%s'." % uuid)
         
-    # try to acquire relation target from session
-    if not ignore_cache:
-        try:
-            return session._d_pending[item.uuid]
-        except (AttributeError, KeyError):
-            pass
-
     # build item
     return build(item.spec, item.uuid)
 
@@ -37,23 +53,23 @@
     return session.query(mapper).filter_by(uuid=uuid)[0]
 
 def persist(item):
-    # create instance
-    instance = factory.create(item.__class__)
+    kls = FACTORY_TYPE_MAP.get(type(item))
+    
+    if kls is not None:
+        instance = factory.create(kls)
+        instance.value = item
+    else:
+        instance = factory.create(item.__class__)
+        update(instance, item)
 
-    # assign uuid to item
-    item._d_uuid = instance.uuid
+    # set soup identifier on instances
+    if type(item) not in BASIC_TYPES:
+        item._d_uuid = instance.uuid
 
-    # hook into transaction
-    try:
-        manager = item._d_manager
-    except AttributeError:
-        manager = item._d_manager = getTransactionManager(item)
-        
-    manager.register()
-
-    # update attributes
-    update(instance, item)
-
+    # register mutable objects with transaction manager
+    if type(item) not in IMMUTABLE_TYPES:
+        registerObject(item, instance.uuid)
+                        
     return instance
 
 def update(instance, item):



More information about the Checkins mailing list