跳转到主要内容

关系对象持久化框架

项目描述

概述

Dobbin 是一个基于 SQLAlchemy 实现的对象数据库。它设计用来模拟 Zope 对象数据库 (ZODB) 的行为,同时提供更大的灵活性和对存储的控制。

它通过利用 zope.schema 的声明性字段定义支持强类型,使用 Python pickle 协议支持弱类型。属性会自动持久化,除了以“_v_”字符开头(易失性属性)的属性外。

用于支持强类型属性的表会动态创建,与没有继承(基接口)的接口一一对应。因此,对象被建模为它们实现的接口之间的连接以及维护对象元数据和弱类型实例属性的表。

作者

此包由 Malthe Borch 和 Stefan Eletzhofer 设计和实现,部分由 Kapil Thangavelu 和 Laurence Rowe 贡献。它以 ZPL 许可。

开发者文档

对象通过其规范进行映射。多态属性声明为接口属性;可以使用模式字段声明强类型;未在模式或接口中声明的属性被认为是易失性的。

唯一标识符(UUID)

使用 16 字节唯一标识符。

关系

多态属性始终使用外键关系进行存储。框架透明地处理这一点。

关系的目标可能是一个基本类型,如字符串、整数、元组或列表,也可能是一个映射对象。

以下字段允许声明任何类型的多态关系,类型在赋值时声明。

  • zope.schema.Object

  • zope.interface.Attribute

集合是经过配置的对象,可以使用序列字段声明

  • zope.schema.List

  • zope.schema.Dict

  • zope.schema.Set

关于字典的说明:字典以(Unicode)字符串为键。可以映射实例用作键,此时使用唯一实例标识符的字符串表示。字典支持在赋值时设置类型的多态值。

框架的概述

本节展示了该包的功能。

简介

Dobbin使用SQLAlchemy的对象关系映射器在数据库中透明地存储对象。对象在两个级别上持久化

属性可以直接对应于表列,在这种情况下,我们说属性是强类型的。这是存储数据的最优方式。

我们还可以存储未直接映射到列的属性;在这种情况下,属性的值以Python pickle的形式存储。这允许弱类型,但也支持非形态数据的持久化,例如不适合自然存储在关系数据库中的数据。

所有对象都自动分配一个全局唯一标识符(UUID)。

我们从一个新的数据库会话开始。

>>> import z3c.saconfig
>>> session = z3c.saconfig.Session()

声明性配置

我们可以使用zope.schema将属性映射到表列。我们依赖模式字段的声明性属性,而不是使用SQL列定义。

我们从一个描述记录专辑的接口开始。

>>> class IAlbum(interface.Interface):
...     artist = schema.TextLine(
...         title=u"Artist",
...         default=u"")
...
...     title = schema.TextLine(
...         title=u"Title",
...         default=u"")

现在我们可以通过使用create方法来创建实现此接口的实例。这是一个设置映射器并通过调用它创建实例的简写。

>>> from z3c.dobbin.factory import create
>>> album = create(IAlbum)

设置属性。

>>> album.artist = "The Beach Boys"
>>> album.title = u"Pet Sounds"

支持接口继承。例如,黑胶唱片是专辑的一种特定类型。

>>> class IVinyl(IAlbum):
...     rpm = schema.Int(
...         title=u"RPM")
>>> vinyl = create(IVinyl)
>>> vinyl.artist = "Diana Ross and The Supremes"
>>> vinyl.title = "Taking Care of Business"
>>> vinyl.rpm = 45

这些属性由SQLAlchemy配置,直接映射到表中的列。

>>> IVinyl.__mapper__.artist
<sqlalchemy.orm.attributes.InstrumentedAttribute object at ...>

CD是另一种类型的专辑。

>>> class ICompactDisc(IAlbum):
...     year = schema.Int(title=u"Year")

让我们选择更近期的Diana Ross,以符合格式。

>>> cd = create(ICompactDisc)
>>> cd.artist = "Diana Ross"
>>> cd.title = "The Great American Songbook"
>>> cd.year = 2005

为了验证我们已经将对象插入到数据库中,我们提交事务,从而刷新当前会话。

>>> session.save(album)
>>> session.save(vinyl)
>>> session.save(cd)

在进行下一步之前,我们必须实际查询数据库一次;这看起来是zope.sqlalchemy的一个错误。

>>> results = session.query(album.__class__).all()

进行事务。

>>> import transaction
>>> transaction.commit()

我们获取数据库元数据对象的引用,以定位每个底层表。

>>> engine = session.bind
>>> metadata = engine.metadata

表根据它们描述的接口点路径命名。提供了一个实用方法来为接口创建合适的表名。

>>> from z3c.dobbin.mapper import encode

验证IVinylIAlbumICompactDisc的表。

>>> session.bind = metadata.bind
>>> session.execute(metadata.tables[encode(IVinyl)].select()).fetchall()
[(2, 45)]
>>> session.execute(metadata.tables[encode(IAlbum)].select()).fetchall()
[(1, u'Pet Sounds', u'The Beach Boys'),
 (2, u'Taking Care of Business', u'Diana Ross and The Supremes'),
 (3, u'The Great American Songbook', u'Diana Ross')]
>>> session.execute(metadata.tables[encode(ICompactDisc)].select()).fetchall()
[(3, 2005)]

映射具体类

现在我们将基于具体类创建一个映射器。我们将让该类实现描述我们想要存储的属性的接口,并提供自定义方法。

>>> class Vinyl(object):
...     interface.implements(IVinyl)
...
...     def __repr__(self):
...         return "<Vinyl %s: %s (@ %d RPM)>" % \
...                (self.artist, self.title, self.rpm)

尽管我们在测试报告中定义的符号表明它们可以从__builtin__模块中访问,但实际上并不是这样。

我们将手动添加这些符号。

>>> import __builtin__
>>> __builtin__.IVinyl = IVinyl
>>> __builtin__.IAlbum = IAlbum
>>> __builtin__.Vinyl = Vinyl

使用create工厂创建一个实例。

>>> vinyl = create(Vinyl)

验证我们已经实例化了我们的类。

>>> isinstance(vinyl, Vinyl)
True

从Diana Ross黑胶唱片复制属性。

>>> diana = session.query(IVinyl.__mapper__).filter_by(
...     artist=u"Diana Ross and The Supremes")[0]
>>> vinyl.artist = diana.artist
>>> vinyl.title = diana.title
>>> vinyl.rpm = diana.rpm

验证我们的Vinyl-类上的方法在映射器上可用。

>>> repr(vinyl)
'<Vinyl Diana Ross and The Supremes: Taking Care of Business (@ 45 RPM)>'

在映射类时,我们可能会遇到应替代列(只读值)的属性。以一个实验性记录类为例,其中转速是标题和艺术家的函数。

>>> class Experimental(Vinyl):
...     @property
...     def rpm(self):
...         return len(self.title+self.artist)
>>> experimental = create(Experimental)

XXX:目前SQLAlchemy存在一个问题,阻碍了这种行为;具体来说,如果我们覆盖的列设置了默认值,它将无法工作。

>>> # session.save(experimental)
>>> experimental.artist = vinyl.artist
>>> experimental.title = vinyl.title

让我们看看这条记录应该播放多快。

>>> experimental.rpm
50

关系

关系是作为其他对象的引用的列。它们使用 zope.schema.Object 字段声明。

请注意,我们不需要事先声明关系的目标类型,尽管在一般情况下,将 schema 关键字参数专业化可能是有用的。

>>> class IFavorite(interface.Interface):
...     item = schema.Object(
...         title=u"Item",
...         schema=interface.Interface)
>>> __builtin__.IFavorite = IFavorite

让我们把我们的Diana Ross记录设为收藏。

>>> favorite = create(IFavorite)
>>> favorite.item = vinyl
>>> favorite.item
<Vinyl Diana Ross and The Supremes: Taking Care of Business (@ 45 RPM)>
>>> session.save(favorite)

我们将提交事务并通过其唯一的ID查找对象。

>>> transaction.commit()
>>> from z3c.dobbin.soup import lookup
>>> favorite = lookup(favorite.uuid)

当我们检索相关项时,它会自动重建以匹配它关联的规范。

>>> favorite.item
<Vinyl Diana Ross and The Supremes: Taking Care of Business (@ 45 RPM)>

我们可以创建指向未映射对象的引用。让我们模拟一个配件项目。

>>> class IAccessory(interface.Interface):
...     name = schema.TextLine(title=u"Name of accessory")
>>> class Accessory(object):
...     interface.implements(IAccessory)
...
...     def __repr__(self):
...          return "<Accessory '%s'>" % self.name

如果我们现在实例化一个配件并将其作为收藏项目分配,我们将隐式创建一个从类规范到数据库的映射器。

>>> cleaner = Accessory()
>>> cleaner.name = u"Record cleaner"

设置关系。

>>> favorite.item = cleaner

让我们尝试获取我们的记录清洁物品。

>>> __builtin__.Accessory = Accessory
>>> favorite.item
<Accessory 'Record cleaner'>

在同一个事务中,关系将返回原始对象,保持完整性。

>>> favorite.item is cleaner
True

会话在事务结束时保持待处理对象的副本。

>>> cleaner in session._d_pending.values()
True

然而,一旦我们提交事务,关系就不再附加到关系源,并且正确的数据将持久保存在数据库中。

>>> cleaner.name = u"CD cleaner"
>>> session.flush()
>>> session.update(favorite)
>>> favorite.item.name
u'CD cleaner'

这种行为在请求-响应类型的环境中应该工作得很好,其中请求通常以提交结束。

集合

我们可以使用序列和映射模式字段来测量像集合一样表现的性质。

让我们将记录集合设置为一个有序列表。

>>> class ICollection(interface.Interface):
...     records = schema.List(
...         title=u"Records",
...         value_type=schema.Object(schema=IAlbum)
...         )
>>> __builtin__.ICollection = ICollection
>>> collection = create(ICollection)
>>> collection.records
[]

添加Diana Ross记录,并将集合保存到会话中。

>>> collection.records.append(diana)
>>> session.save(collection)

我们可以取回我们的集合。

>>> collection = lookup(collection.uuid)

让我们验证我们已经存储了Diana Ross记录。

>>> record = collection.records[0]
>>> record.artist, record.title
(u'Diana Ross and The Supremes', u'Taking Care of Business')
>>> session.flush()

当我们创建一个新的、瞬时的对象并将其附加到列表中时,它将自动在会话中保存。

>>> collection = lookup(collection.uuid)
>>> kool = create(IVinyl)
>>> kool.artist = u"Kool & the Gang"
>>> kool.title = u"Music Is the Message"
>>> kool.rpm = 33
>>> collection.records.append(kool)
>>> [record.artist for record in collection.records]
[u'Diana Ross and The Supremes', u'Kool & the Gang']
>>> session.flush()
>>> session.update(collection)

我们可以删除项目。

>>> collection.records.remove(kool)
>>> len(collection.records) == 1
True

并且扩展。

>>> collection.records.extend((kool,))
>>> len(collection.records) == 2
True

项目可以出现在列表中两次。

>>> collection.records.append(kool)
>>> len(collection.records) == 3
True

我们可以在集合中添加具体实例。

>>> marvin = Vinyl()
>>> marvin.artist = u"Marvin Gaye"
>>> marvin.title = u"Let's get it on"
>>> marvin.rpm = 33
>>> collection.records.append(marvin)
>>> len(collection.records) == 4
True

并且删除它们,也是如此。

>>> collection.records.remove(marvin)
>>> len(collection.records) == 3
True

标准列表方法都是可用的。

>>> collection.records = [marvin, vinyl]
>>> collection.records.sort(key=lambda record: record.artist)
>>> collection.records
[<Vinyl Diana Ross and The Supremes: Taking Care of Business (@ 45 RPM)>,
 <Vinyl Marvin Gaye: Let's get it on (@ 33 RPM)>]
>>> collection.records.reverse()
>>> collection.records
[<Vinyl Marvin Gaye: Let's get it on (@ 33 RPM)>,
 <Vinyl Diana Ross and The Supremes: Taking Care of Business (@ 45 RPM)>]
>>> collection.records.index(vinyl)
1
>>> collection.records.pop()
<Vinyl Diana Ross and The Supremes: Taking Care of Business (@ 45 RPM)>
>>> collection.records.insert(0, vinyl)
>>> collection.records
[<Vinyl Diana Ross and The Supremes: Taking Care of Business (@ 45 RPM)>,
 <Vinyl Marvin Gaye: Let's get it on (@ 33 RPM)>]
>>> collection.records.count(vinyl)
1
>>> collection.records[1] = vinyl
>>> collection.records.count(vinyl)
2

为了保险起见,让我们创建一个不向其列表添加任何元素的新实例。

>>> empty_collection = create(ICollection)
>>> session.save(empty_collection)

为了演示映射实现,让我们为记录集合设置一个目录。我们将按ASIN字符串索引记录。

>>> class ICatalog(interface.Interface):
...     index = schema.Dict(
...         title=u"Record index")
>>> catalog = create(ICatalog)
>>> session.save(catalog)

向索引中添加一条记录。

>>> catalog.index[u"B00004WZ5Z"] = diana
>>> catalog.index[u"B00004WZ5Z"]
<Mapper (__builtin__.IVinyl) at ...>

检查提交后的状态。

>>> transaction.commit()
>>> catalog.index[u"B00004WZ5Z"]
<Mapper (__builtin__.IVinyl) at ...>

让我们检查是否支持标准字典方法。

>>> catalog.index.values()
[<Mapper (__builtin__.IVinyl) at ...>]
>>> tuple(catalog.index.itervalues())
(<Mapper (__builtin__.IVinyl) at ...>,)
>>> catalog.index.setdefault(u"B00004WZ5Z", None)
<Mapper (__builtin__.IVinyl) at ...>
>>> catalog.index.pop(u"B00004WZ5Z")
<Mapper (__builtin__.IVinyl) at ...>
>>> len(catalog.index)
0

支持具体实例。

>>> vinyl = Vinyl()
>>> vinyl.artist = diana.artist
>>> vinyl.title = diana.title
>>> vinyl.rpm = diana.rpm
>>> catalog.index[u"B00004WZ5Z"] = vinyl
>>> len(catalog.index)
1
>>> catalog.index.popitem()
(u'B00004WZ5Z',
 <Vinyl Diana Ross and The Supremes: Taking Care of Business (@ 45 RPM)>)
>>> catalog.index = {u"B00004WZ5Z": vinyl}
>>> len(catalog.index)
1
>>> catalog.index.clear()
>>> len(catalog.index)
0

我们可以使用映射对象作为索引。

>>> catalog.index[diana] = diana
>>> catalog.index.keys()[0] == diana.uuid
True
>>> transaction.commit()
>>> catalog.index[diana]
<Mapper (__builtin__.IVinyl) at ...>
>>> class IDiscography(ICatalog):
...     records = schema.Dict(
...         title=u"Discographies by artist",
...         value_type=schema.List())

非形态对象

我们可以设置和检索未在接口中声明的属性。

>>> record = create(interface.Interface)
>>> 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'

使用这种类型的弱引用,我们可以存储(几乎)任何类型的结构。值以Python pickles的形式保留。

>>> favorite = create(interface.Interface)
>>> session.save(favorite)

事务钩子确保在会话期间分配的值是瞬时的。

>>> obj = object()
>>> favorite.item = obj
>>> favorite.item is obj
True

整数、浮点数和unicode字符串是直接的。

>>> 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.'

普通字符串需要显式转换为 str

>>> favorite.item = "My favorite number is 42."; transaction.commit()
>>> str(favorite.item)
'My favorite number is 42.'

或项目的序列。

>>> favorite.item = (u"green", u"blue", u"red"); transaction.commit()
>>> favorite.item
(u'green', u'blue', u'red')

字典。

>>> favorite.item = {u"green": 0x00FF00, u"blue": 0x0000FF, u"red": 0xFF0000}
>>> transaction.commit()
>>> favorite.item
{u'blue': 255, u'green': 65280, u'red': 16711680}
>>> favorite.item[u"black"] = 0x000000
>>> sorted(favorite.item.items())
[(u'black', 0), (u'blue', 255), (u'green', 65280), (u'red', 16711680)]

我们需要显式设置此实例的脏位。

>>> favorite.item = favorite.item
>>> transaction.commit()
>>> sorted(favorite.item.items())
[(u'black', 0), (u'blue', 255), (u'green', 65280), (u'red', 16711680)]

当我们创建到可变对象的引用时,钩子会进入事务机制以跟踪待处理状态。

>>> some_list = [u"green", u"blue"]
>>> favorite.item = some_list
>>> some_list.append(u"red"); transaction.commit()
>>> favorite.item
[u'green', u'blue', u'red']

非形态结构。

>>> favorite.item = ((1, u"green"), (2, u"blue"), (3, u"red")); transaction.commit()
>>> favorite.item
((1, u'green'), (2, u'blue'), (3, u'red'))

涉及与其他实例相关的关系的结构。

>>> favorite.item = vinyl; transaction.commit()
>>> favorite.item
<Vinyl Diana Ross and The Supremes: Taking Care of Business (@ 45 RPM)>

自引用工作是因为多态属性是延迟的。

>>> favorite.item = favorite; transaction.commit()
>>> favorite.item
<z3c.dobbin.bootstrap.Soup object at ...>

安全

来自Zope的安全模型应用于映射器。

>>> from zope.security.checker import getCheckerForInstancesOf

我们的 Vinyl 类没有定义安全检查器。

>>> from z3c.dobbin.mapper import getMapper
>>> mapper = getMapper(Vinyl)
>>> getCheckerForInstancesOf(mapper) is None
True

让我们设置检查器并重新生成映射器。

>>> from zope.security.checker import defineChecker, CheckerPublic
>>> defineChecker(Vinyl, CheckerPublic)
>>> from z3c.dobbin.mapper import createMapper
>>> mapper = createMapper(Vinyl)
>>> getCheckerForInstancesOf(mapper) is CheckerPublic
True

已知限制

某些名称是不允许的,在构建映射器时将被忽略。

>>> class IKnownLimitations(interface.Interface):
...     __name__ = schema.TextLine()
>>> from z3c.dobbin.interfaces import IMapper
>>> mapper = IMapper(IKnownLimitations)
>>> mapper.__name__
'Mapper'

清理

提交会话。

>>> transaction.commit()

变更日志

0.4dev

0.4.2

  • 与SQLAlchemy 0.5rc1兼容。

0.4.1

  • 修复版本问题。

0.4.0

  • 添加补丁以支持映射类继承树中的旧式混入类。

  • 所有未声明为接口名称的属性现在都使用Pickle协议自动持久化。此规则的例外是以下划线“_v_”开头的属性(易变属性)。

  • 更改目标为SQLAlchemy 0.5系列。

0.3.2

  • 使用pickle存储多态属性;对于非形态数据,使用原生列没有好处。

  • Dobbin现在使用zope.sqlalchemy进行事务和会话粘合。

0.3.1

  • 使用本地UUID列类型(可在PostgreSQL上使用);由于其弱类型,保留与SQLite的兼容性。

  • 基本类型工厂现在注册为组件。

0.3.0

  • 实现了列表方法的其余部分。

  • 重构表引导;内部表现在使用一个不太可能与现有表冲突的命名约定。

  • 添加了对schema.Dict(包括多态字典关系)的支持。

  • 为基本类型(int、str、unicode、tuple和list)的子集实现了多态关系。

0.2.9

  • 现在仅在最小接口上创建一次表;这修复了在创建具有显式多态类的映射器时在SQLite和Postgres上存在的问题。

  • 变更日志的第一条条目。

项目详情


下载文件

下载适合您平台文件的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分布

z3c.dobbin-0.4.2.tar.gz(30.2 KB 查看哈希值

上传时间

由以下机构支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面