[Checkins] SVN: z3c.dobbin/trunk/ Reimplemented persistence policy
such that all non-mapped attributes are persisted
automatically using the Pickle-protocol.
Malthe Borch
mborch at gmail.com
Tue Jul 22 09:04:46 EDT 2008
Log message for revision 88685:
Reimplemented persistence policy such that all non-mapped attributes are persisted automatically using the Pickle-protocol.
Changed:
U z3c.dobbin/trunk/CHANGES.txt
U z3c.dobbin/trunk/README.txt
U z3c.dobbin/trunk/setup.py
U z3c.dobbin/trunk/src/z3c/dobbin/README.txt
U z3c.dobbin/trunk/src/z3c/dobbin/bootstrap.py
U z3c.dobbin/trunk/src/z3c/dobbin/mapper.py
U z3c.dobbin/trunk/src/z3c/dobbin/session.py
U z3c.dobbin/trunk/src/z3c/dobbin/testing.py
A z3c.dobbin/trunk/src/z3c/dobbin/utility.py
U z3c.dobbin/trunk/src/z3c/dobbin/zs2sa.py
-=-
Modified: z3c.dobbin/trunk/CHANGES.txt
===================================================================
--- z3c.dobbin/trunk/CHANGES.txt 2008-07-22 12:56:23 UTC (rev 88684)
+++ z3c.dobbin/trunk/CHANGES.txt 2008-07-22 13:04:45 UTC (rev 88685)
@@ -1,6 +1,16 @@
Change log
==========
+0.4dev
+------
+
+- All attributes that are not declared as interface names are now
+ persisted automatically using the Pickle-protocol. The exception to
+ this rule is attributes starting with the characters "_v_" (volatile
+ attributes).
+
+- Changed target to SQLAlchemy 0.5-series.
+
0.3.2
-----
@@ -15,35 +25,29 @@
- Use native UUID column type (available on PostgreSQL); compatibility
with SQLite is preserved due to its weak typing.
- [malthe]
+
- Basic type factories are now registered as components.
- [malthe]
0.3.0
-----
- Implemented rest of list methods.
- [malthe]
- Refactoring of table bootstrapping; internal tables now using a
naming convention less likely to clash with existing tables.
- [malthe]
- Added support for ``schema.Dict`` (including polymorphic dictionary
relation).
- [malthe]
- Implemented polymorphic relations for a subset of the basic types
(int, str, unicode, tuple and list).
- [malthe]
-
+
0.2.9
-----
- Tables are now only created once per minimal interface; this fixes
issue on both SQLite and Postgres when we create mappers with an
explicit polymorphic class.
- [malthe]
- First entry in change-log.
Modified: z3c.dobbin/trunk/README.txt
===================================================================
--- z3c.dobbin/trunk/README.txt 2008-07-22 12:56:23 UTC (rev 88684)
+++ z3c.dobbin/trunk/README.txt 2008-07-22 13:04:45 UTC (rev 88685)
@@ -1,21 +1,26 @@
Overview
========
-Dobbin is a relational database abstraction layer supporting a
-semi-transparent object persistance model.
+Dobbin is an object database implemented on top of SQLAlchemy. It's
+designed to mimick the behavior of the Zope object database (ZODB)
+while providing greater flexibility and control of the storage.
-It relies on descriptive attribute and field declarations based on
-zope.interface and zope.schema. Strong typing is supported (and
-encouraged when possible), but not required.
+It supports strong typing with native SQL columns by utilizing the
+declarative field definitions from zope.schema. Weak typing is
+supported using the Python pickle protocol. Attributes are
+automatically persisted with the exception of those starting with the
+characters "_v_" (volatile attributes).
-Tables are created on-the-fly with 1:1 correspondence to interfaces
-with no inheritance (base interface). As such, objects are modelled as
-a join between the interfaces they implement.
+Tables to support the strongly typed attributes are created on-the-fly
+with a 1:1 correspondence to interfaces with no inheritance (base
+interface). As such, objects are modelled as a join between the
+interfaces they implement plus a table that maintains object metadata
+and weakly typed instance attributes.
Authors
-------
-This package was designed and implemented by Malthe Borch, Stefan
+This package was designed and implemented by Malthe Borch and Stefan
Eletzhofer with parts contributed by Kapil Thangavelu and Laurence
Rowe. It's licensed as ZPL.
Modified: z3c.dobbin/trunk/setup.py
===================================================================
--- z3c.dobbin/trunk/setup.py 2008-07-22 12:56:23 UTC (rev 88684)
+++ z3c.dobbin/trunk/setup.py 2008-07-22 13:04:45 UTC (rev 88685)
@@ -18,7 +18,7 @@
return "\n".join((open(f).read() for f in files))
setup(name='z3c.dobbin',
- version='0.3dev',
+ version='0.4dev',
license='ZPL',
author = "Malthe Borch, Stefan Eletzhofer and the Zope Community",
author_email = "zope-dev at zope.org",
@@ -51,5 +51,5 @@
'zope.configuration',
'z3c.saconfig',
'transaction',
- 'SQLAlchemy==0.4.6'],
+ 'SQLAlchemy>0.4'],
)
Modified: z3c.dobbin/trunk/src/z3c/dobbin/README.txt
===================================================================
--- z3c.dobbin/trunk/src/z3c/dobbin/README.txt 2008-07-22 12:56:23 UTC (rev 88684)
+++ z3c.dobbin/trunk/src/z3c/dobbin/README.txt 2008-07-22 13:04:45 UTC (rev 88685)
@@ -1,21 +1,38 @@
Walk-through of the framework
=============================
-This section demonstrates the main functionality of the package using
-the doctest format.
+This section demonstrates the functionality of the package.
+Introduction
+------------
+
+Dobbin uses SQLAlchemy's object relational mapper to transparently
+store objects in the database. Objects are persisted in two levels:
+
+Attributes may correspond directly to a table column in which case we
+say that the attribute is strongly typed. This is the most optimal
+way to store data.
+
+We may also store attributes that are not mapped directly to a column;
+in this case, the value of the attribute is stored as a Python
+pickle. This allows weak typing, but also persistence of amorphic
+data, e.g. data which does not fit naturally in a relational database.
+
+A universally unique id (UUID) is automatically assigned to all
+objects.
+
We begin with a new database session.
>>> import z3c.saconfig
>>> session = z3c.saconfig.Session()
+
+Declarative configuration
+-------------------------
-Mappers from interface specification
-------------------------------------
+We can map attributes to table columns using zope.schema. Instead of
+using SQL column definitions, we rely on the declarative properties of
+schema fields.
-We'll start out creating mappers directly from an interface
-specification. The instances will only afford access to the declared
-attributes and have no methods.
-
We start out with an interface decribing a recorded album.
>>> class IAlbum(interface.Interface):
@@ -28,8 +45,8 @@
... default=u"")
We can now fabricate instances that implement this interface by using
-the ``create`` method. This is a shorthand for setting up the mapper
-and creating an instance using its factory.
+the ``create`` method. This is a shorthand for setting up a mapper and
+creating an instance by calling it.
>>> from z3c.dobbin.factory import create
>>> album = create(IAlbum)
@@ -44,30 +61,27 @@
>>> class IVinyl(IAlbum):
... rpm = schema.Int(
- ... title=u"RPM",
- ... default=33)
+ ... title=u"RPM")
>>> vinyl = create(IVinyl)
-
-What actually happens on the database side is that columns are mapped
-to the interface that they provide.
-
-Let's demonstrate that the mapper instance actually implements the
-defined fields.
-
>>> vinyl.artist = "Diana Ross and The Supremes"
>>> vinyl.title = "Taking Care of Business"
>>> vinyl.rpm = 45
-Or a compact disc.
+The attributes are instrumented by SQLAlchemy and map directly to a
+column in a table.
+ >>> IVinyl.__mapper__.artist
+ <sqlalchemy.orm.attributes.InstrumentedAttribute object at ...>
+
+A compact disc is another kind of album.
+
>>> class ICompactDisc(IAlbum):
... year = schema.Int(title=u"Year")
- >>> cd = create(ICompactDisc)
-
Let's pick a more recent Diana Ross, to fit the format.
+ >>> cd = create(ICompactDisc)
>>> cd.artist = "Diana Ross"
>>> cd.title = "The Great American Songbook"
>>> cd.year = 2005
@@ -83,6 +97,8 @@
to be a bug in ``zope.sqlalchemy``.
>>> results = session.query(album.__class__).all()
+
+Proceed with the transaction.
>>> import transaction
>>> transaction.commit()
@@ -113,8 +129,8 @@
>>> session.execute(metadata.tables[encode(ICompactDisc)].select()).fetchall()
[(3, 2005)]
-Concrete class specification
-----------------------------
+Mapping concrete classes
+------------------------
Now we'll create a mapper based on a concrete class. We'll let the
class implement the interface that describes the attributes we want to
@@ -123,9 +139,6 @@
>>> class Vinyl(object):
... interface.implements(IVinyl)
...
- ... artist = title = u""
- ... rpm = 33
- ...
... def __repr__(self):
... return "<Vinyl %s: %s (@ %d RPM)>" % \
... (self.artist, self.title, self.rpm)
@@ -162,8 +175,10 @@
>>> repr(vinyl)
'<Vinyl Diana Ross and The Supremes: Taking Care of Business (@ 45 RPM)>'
-If we're mapping a concrete class, and run into class properties, we
-won't instrument them even if they're declared by the schema.
+When mapping a class we may run into properties that should take the
+place of a column (a read-only value). As an example, consider this
+experimental record class where rotation speed is a function of the
+title and artist.
>>> class Experimental(Vinyl):
... @property
@@ -171,6 +186,13 @@
... return len(self.title+self.artist)
>>> experimental = create(Experimental)
+
+XXX: There's currently an issue with SQLAlchemy that hinders this
+behavior; it specifically won't work if a default value is set on the
+column that we're overriding.
+
+ >>> # session.save(experimental)
+
>>> experimental.artist = vinyl.artist
>>> experimental.title = vinyl.title
@@ -179,26 +201,20 @@
>>> experimental.rpm
50
-Instances of mappers automatically join the object soup.
-
- >>> from z3c.dobbin.mapper import getMapper
- >>> mapper = getMapper(Vinyl)
- >>> instance = mapper()
- >>> instance.uuid is not None
- True
-
Relations
---------
-Relations are columns that act as references to other objects.
+Relations are columns that act as references to other objects. They're
+declared using the ``zope.schema.Object`` field.
-As an example, let's create an object holds a reference to some
-favorite item. We use ``zope.schema.Object`` to declare this
-reference; relations are polymorphic and we needn't declare the schema
-of the referenced object in advance.
+Note that we needn't declare the relation target type in advance,
+although it may be useful in general to specialize the ``schema``
+keyword parameter.
>>> class IFavorite(interface.Interface):
- ... item = schema.Object(title=u"Item", schema=interface.Interface)
+ ... item = schema.Object(
+ ... title=u"Item",
+ ... schema=interface.Interface)
>>> __builtin__.IFavorite = IFavorite
@@ -211,7 +227,7 @@
>>> session.save(favorite)
-We'll commit the transaction and lookup the object by its UUID.
+We'll commit the transaction and lookup the object by its unique id.
>>> transaction.commit()
@@ -485,18 +501,29 @@
... title=u"Discographies by artist",
... value_type=schema.List())
-Polymorphic structures
-----------------------
+Amorphic objects
+----------------
-We can use weak typing to store (almost) any kind of structure. Values
-are kept as Python pickles.
+We can set and retrieve attributes that aren't declared in an
+interface.
- >>> class IPolyFavorite(interface.Interface):
- ... item = interface.Attribute(u"Any kind of favorite")
+ >>> record = create(interface.Interface)
- >>> __builtin__.IPolyFavorite = IPolyFavorite
- >>> favorite = create(IPolyFavorite)
+ >>> record.publisher = u"Columbia records"
+ >>> record.publisher
+ u'Columbia records'
+ >>> session.save(record)
+ >>> session.query(record.__class__).filter_by(
+ ... uuid=record.uuid)[0].publisher
+ u'Columbia records'
+
+Using this kind of weak we can store (almost) any kind of
+structure. Values are kept as Python pickles.
+
+ >>> favorite = create(interface.Interface)
+ >>> session.save(favorite)
+
A transaction hook makes sure that assigned values are transient
during a session.
@@ -547,12 +574,9 @@
>>> favorite.item = favorite.item
>>> transaction.commit()
-Clear the object cache and verify value.
-
- >>> del favorite._v_cached_item_pickle
>>> sorted(favorite.item.items())
[(u'black', 0), (u'blue', 255), (u'green', 65280), (u'red', 16711680)]
-
+
When we create relations to mutable objects, a hook is made into the
transaction machinery to keep track of the pending state.
@@ -571,18 +595,14 @@
Structures involving relations to other instances.
>>> favorite.item = vinyl; transaction.commit()
- >>> del favorite._v_cached_item_pickle
>>> favorite.item
<Vinyl Diana Ross and The Supremes: Taking Care of Business (@ 45 RPM)>
Self-referencing works because polymorphic attributes are lazy.
- >>> session.save(favorite)
-
>>> favorite.item = favorite; transaction.commit()
- >>> del favorite._v_cached_item_pickle
>>> favorite.item
- <Mapper (__builtin__.IPolyFavorite) at ...>
+ <z3c.dobbin.bootstrap.Soup object at ...>
Security
--------
@@ -593,7 +613,9 @@
Our ``Vinyl`` class does not have a security checker defined.
+ >>> from z3c.dobbin.mapper import getMapper
>>> mapper = getMapper(Vinyl)
+
>>> getCheckerForInstancesOf(mapper) is None
True
Modified: z3c.dobbin/trunk/src/z3c/dobbin/bootstrap.py
===================================================================
--- z3c.dobbin/trunk/src/z3c/dobbin/bootstrap.py 2008-07-22 12:56:23 UTC (rev 88684)
+++ z3c.dobbin/trunk/src/z3c/dobbin/bootstrap.py 2008-07-22 13:04:45 UTC (rev 88685)
@@ -1,3 +1,4 @@
+from zope import interface
from zope import component
import sqlalchemy as rdb
@@ -6,27 +7,25 @@
from z3c.saconfig import Session
import soup
+import utility
import relations
import interfaces
+import cPickle
class UUID(rdb.types.TypeEngine):
def get_col_spec(self):
return "UUID"
-def bootstrapDatabaseEngine(event=None):
- session = Session()
- engine = session.bind
- engine.metadata = metadata = rdb.MetaData(engine)
+def initialize(event=None):
+ """Database initialization.
- # setup metadata
- setUp(metadata)
-
-def setUp(metadata):
- """Table setup.
-
This method sets up the tables that are necessary for the
operation of the persistence and relational framework.
"""
+
+ session = Session()
+ engine = session.bind
+ engine.metadata = metadata = rdb.MetaData(engine)
soup = rdb.Table(
'dobbin:soup',
@@ -34,10 +33,11 @@
rdb.Column('id', rdb.Integer, primary_key=True, autoincrement=True),
rdb.Column('uuid', UUID, unique=True, index=True),
rdb.Column('spec', rdb.String, index=True),
+ rdb.Column('dict', rdb.PickleType, default={}, index=False),
)
soup_fk = rdb.ForeignKey(soup.c.uuid)
-
+
int_relation = rdb.Table(
'dobbin:relation:int',
metadata,
@@ -63,11 +63,18 @@
metadata.create_all()
class Soup(object):
+ interface.implements(interfaces.IMapped)
+
"""Soup class.
This is the base object of all mappers.
"""
+ def __new__(cls, *args, **kwargs):
+ inst = object.__new__(cls, *args, **kwargs)
+ inst.__dict__ = utility.dictproxy(inst)
+ return inst
+
def __cmp__(self, other):
if interfaces.IMapped.providedBy(other):
return cmp(self.id, other.id)
@@ -75,4 +82,4 @@
return -1
def __reduce__(self):
- return (soup.lookup, (self.uuid,))
+ return (soup.build, (self.__spec__, self.uuid,))
Modified: z3c.dobbin/trunk/src/z3c/dobbin/mapper.py
===================================================================
--- z3c.dobbin/trunk/src/z3c/dobbin/mapper.py 2008-07-22 12:56:23 UTC (rev 88684)
+++ z3c.dobbin/trunk/src/z3c/dobbin/mapper.py 2008-07-22 13:04:45 UTC (rev 88685)
@@ -22,6 +22,7 @@
from itertools import chain
import bootstrap
+import session as tx
import soup
import zs2sa
import types
@@ -54,6 +55,8 @@
def createMapper(spec):
"""Create a mapper for the specification."""
+ interface.alsoProvides(spec, IMapped)
+
engine = Session().bind
metadata = engine.metadata
@@ -73,12 +76,14 @@
if isinstance(value, property):
exclude.append(name)
- assert ifaces, "Specification must declare at least one field."
-
+ if not ifaces:
+ spec.__mapper__ = bootstrap.Soup
+ return spec.__mapper__
+
# create joined table
properties = {}
first_table = None
-
+
for (t, p) in (getTable(iface, metadata, exclude) for iface in ifaces):
if first_table is None:
table = first_table = t
@@ -89,10 +94,10 @@
specification_path = '%s.%s' % (spec.__module__, spec.__name__)
class Mapper(bootstrap.Soup, kls):
- interface.implements(IMapped, *ifaces)
+ interface.implements(*ifaces)
__spec__ = specification_path
-
+
def __init__(self, *args, **kwargs):
super(Mapper, self).__init__(*args, **kwargs)
@@ -125,7 +130,9 @@
del properties[name]
setattr(Mapper, name, prop)
- soup_table = bootstrap.Soup.c.id.table
+ # XXX: there must be a more straight-forward way to do this
+ soup_table = bootstrap.Soup._sa_class_manager.mappers[None].local_table
+
polymorphic = (
[Mapper], table.join(
soup_table, first_table.c.id==soup_table.c.id))
@@ -140,8 +147,7 @@
inherit_condition=(first_table.c.id==soup_table.c.id))
spec.__mapper__ = Mapper
- interface.alsoProvides(spec, IMapped)
-
+
return Mapper
def removeMapper(spec):
@@ -187,13 +193,15 @@
kw = dict(useexisting=True)
+ soup_table = bootstrap.Soup._sa_class_manager.mappers[None].local_table
+
table = rdb.Table(
name,
metadata,
- rdb.Column('id', rdb.Integer, rdb.ForeignKey(bootstrap.Soup.c.id), primary_key=True),
+ rdb.Column('id', rdb.Integer, rdb.ForeignKey(soup_table.c.id), primary_key=True),
*columns,
**kw)
- metadata.create_all(checkfirst=True)
+ table.create(checkfirst=True)
return table, properties
Modified: z3c.dobbin/trunk/src/z3c/dobbin/session.py
===================================================================
--- z3c.dobbin/trunk/src/z3c/dobbin/session.py 2008-07-22 12:56:23 UTC (rev 88684)
+++ z3c.dobbin/trunk/src/z3c/dobbin/session.py 2008-07-22 13:04:45 UTC (rev 88685)
@@ -7,9 +7,6 @@
def COPY_CONCRETE_TO_INSTANCE(uuid):
return COPY_CONCRETE_TO_INSTANCE, uuid
-def COPY_VALUE_TO_INSTANCE(uuid, name):
- return COPY_VALUE_TO_INSTANCE, uuid, name
-
def addBeforeCommitHook(token, value, hook):
session = Session()
Modified: z3c.dobbin/trunk/src/z3c/dobbin/testing.py
===================================================================
--- z3c.dobbin/trunk/src/z3c/dobbin/testing.py 2008-07-22 12:56:23 UTC (rev 88684)
+++ z3c.dobbin/trunk/src/z3c/dobbin/testing.py 2008-07-22 13:04:45 UTC (rev 88685)
@@ -20,11 +20,11 @@
component.provideUtility(factory)
# setup scoped session
- utility = GloballyScopedSession(autoflush=True)
+ utility = GloballyScopedSession()
component.provideUtility(utility)
# bootstrap database engine
- z3c.dobbin.bootstrap.bootstrapDatabaseEngine()
+ z3c.dobbin.bootstrap.initialize()
# register components
zope.configuration.xmlconfig.XMLConfig('meta.zcml', component)()
Added: z3c.dobbin/trunk/src/z3c/dobbin/utility.py
===================================================================
--- z3c.dobbin/trunk/src/z3c/dobbin/utility.py (rev 0)
+++ z3c.dobbin/trunk/src/z3c/dobbin/utility.py 2008-07-22 13:04:45 UTC (rev 88685)
@@ -0,0 +1,40 @@
+import soup
+import session as tx
+import interfaces
+
+class dictproxy(dict):
+ """Dictionary proxy.
+
+ Proxies non-mapped attributes to an internal dictionary.
+ """
+
+ def __init__(self, owner):
+ assert interfaces.IMapped.providedBy(owner)
+ self.owner = owner
+
+ def _is_mapped(self, key):
+ owner = self.owner
+ return key.startswith('_v_') or \
+ key in owner.__class__.__dict__ or \
+ key in owner._sa_class_manager.keys()
+
+ def __getitem(self, key):
+ if self._is_mapped(key):
+ return dict.__getitem__(key)
+
+ if self.owner.dict is None:
+ raise KeyError(key)
+
+ return self.owner.dict[key]
+
+ def __setitem__(self, key, value):
+ if self._is_mapped(key):
+ dict.__setitem__(self, key, value)
+ else:
+ if self.owner.dict is None:
+ self.owner.dict = {key: value}
+ else:
+ self.owner.dict[key] = value
+
+ def __repr__(self):
+ return "<dictproxy %s>" % dict.__repr__(self)
Modified: z3c.dobbin/trunk/src/z3c/dobbin/zs2sa.py
===================================================================
--- z3c.dobbin/trunk/src/z3c/dobbin/zs2sa.py 2008-07-22 12:56:23 UTC (rev 88684)
+++ z3c.dobbin/trunk/src/z3c/dobbin/zs2sa.py 2008-07-22 13:04:45 UTC (rev 88685)
@@ -24,8 +24,6 @@
import relations
import collections
-import cPickle as Pickle
-
class FieldTranslator(object):
"""Translate a zope schema field to a SQLAlchemy column."""
@@ -78,66 +76,6 @@
return rdb.Column(
field.__name__+'_uuid', bootstrap.UUID, nullable=False)
-class PickleTranslator(object):
- def __call__(self, field, metadata):
- return rdb.Column(
- field.__name__+'_pickle', rdb.BLOB, nullable=True)
-
-class PickleProperty(property):
- def __init__(self, name):
- self.name = name
- self.cache = '_v_cached_'+name
- property.__init__(self, self._get, self._set)
-
- def _get(self, obj, type=None):
- session = Session()
- name = self.name
- cache = self.cache
-
- token = tx.COPY_VALUE_TO_INSTANCE(obj.uuid, name)
-
- # check pending objects
- try:
- return session._d_pending[token]
- except (AttributeError, KeyError):
- pass
-
- # check object cache
- value = getattr(obj, cache, None)
- if value is not None:
- return value
-
- # load pickle
- pickle = getattr(obj, name)
- value = pickle and Pickle.loads(pickle)
-
- # update cache
- if value is not None:
- setattr(obj, cache, value)
-
- return value
-
- def _set(self, obj, value):
- name = self.name
- token = tx.COPY_VALUE_TO_INSTANCE(obj.uuid, name)
-
- def copy_value_to_instance():
- value = Session()._d_pending[token]
- pickle = Pickle.dumps(value)
- setattr(obj, name, pickle)
-
- # add transaction hook
- tx.addBeforeCommitHook(
- token, value, copy_value_to_instance)
-
- # update cache
- if value is not None:
- setattr(obj, self.cache, value)
-
-class PicklePropertyFactory(object):
- def __call__(self, field, column, metadata):
- return {field.__name__: PickleProperty(column.name)}
-
class ObjectProperty(object):
"""Object property.
@@ -152,7 +90,7 @@
field.__name__: relation,
relation.name: orm.relation(
bootstrap.Soup,
- primaryjoin=bootstrap.Soup.c.uuid==column,
+ primaryjoin=bootstrap.Soup.uuid==column,
foreign_keys=[column],
enable_typechecks=False,
lazy=True)
@@ -181,7 +119,7 @@
relation_class = relations.OrderedRelation
def getPrimaryJoinCondition(self):
- return bootstrap.Soup.c.uuid==relations.OrderedRelation.c.left
+ return bootstrap.Soup.uuid==relations.OrderedRelation.left
class TupleProperty(ListProperty):
collection_class = collections.Tuple
@@ -191,7 +129,7 @@
relation_class = relations.KeyRelation
def getPrimaryJoinCondition(self):
- return bootstrap.Soup.c.uuid==relations.KeyRelation.c.left
+ return bootstrap.Soup.uuid==relations.KeyRelation.left
fieldmap = {
schema.ASCII: StringTranslator(),
@@ -214,6 +152,6 @@
schema.Text: StringTranslator(rdb.UnicodeText),
schema.TextLine: StringTranslator(rdb.Unicode),
schema.URI: StringTranslator(rdb.Unicode),
- interface.Attribute: (PickleTranslator(), PicklePropertyFactory()),
+ interface.Attribute: None,
interface.interface.Method: None,
}
More information about the Checkins
mailing list