[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