跳转到主要内容

基于SQLAlchemy的Grok RDB支持。

项目描述

megrok.rdb

简介

megrok.rdb包基于强大的SQLAlchemy库,为Grok添加了强大的关系数据库支持。它提供了新的megrok.rdb.Modelmegrok.rdb.Container,它们的行为与核心Grok中的类似,但背后是关系数据库。

在本文档中,我们将向您展示如何使用megrok.rdb

声明性模型

megrok.rdb几乎直接使用了SQLAlchemy的ORM系统,特别是它的声明性扩展。它只提供了一些特殊的基类和指令以简化操作,并提供了一些其他方便的功能,有助于与Grok集成。

我们首先导入后续需要的SQLAlchemy组件。

>>> from sqlalchemy import Column, ForeignKey
>>> from sqlalchemy.types import Integer, String
>>> from sqlalchemy.orm import relation

SQLAlchemy将数据库模式信息组织到一个称为MetaData的单元中。该模式可以从数据库模式中反射出来,也可以从Python中定义的模式创建。使用megrok.rdb,我们通常后者,在ORM中映射到的内容类内部。我们需要一些元数据来关联我们的内容类。

让我们设置元数据对象

>>> from megrok import rdb
>>> metadata = rdb.MetaData()

现在我们将设置几个内容类。我们将有一个非常简单的结构,其中(大学)系与零个或多个与其关联的课程。首先我们将定义一个可以包含课程的容器

>>> class Courses(rdb.Container):
...    pass

这就结束了。如果没有使用rdb.key指令,容器中的键将被定义为数据库中的(可能自动分配的)主键。

FIXME 使某些东西在doctests中正常工作的一个技巧。在某些特定的设置中,这个技巧不再需要,但我目前无法重新建立这个包的组合

>>> __file__ = 'foo'

现在我们可以设置Department类。这个类有一个courses关系,它链接到其课程

>>> class Department(rdb.Model):
...   rdb.metadata(metadata)
...
...   id = Column('id', Integer, primary_key=True)
...   name = Column('name', String(50))
...
...   courses = relation('Course',
...                       backref='department',
...                       collection_class=Courses)

这与使用sqlalchemy.ext.declarative的方式非常相似,但也有一些区别

* we inherit from ``rdb.Model`` to make this behave like a Grok model.
  • 我们不需要使用__tablename__来设置表名。默认情况下,表名将是类名的小写形式,但您可以使用rdb.tablename指令来覆盖它。

  • 我们需要明确使用哪个元数据对象。我们在测试中这样做,但在Grok应用程序中,只需在模块级别使用rdb.metadata指令,就可以自动将所有rdb类关联到该元数据对象。

  • 我们标记courses关系使用我们之前定义的Courses容器类。这是一个正常的SQLAlchemy功能,只是如果我们想使用Grok风格的容器,我们必须使用它。

我们通过定义Course类来完成我们的数据库定义

>>> class Course(rdb.Model):
...   rdb.metadata(metadata)
...
...   id = Column('id', Integer, primary_key=True)
...   department_id = Column('department_id', Integer,
...                           ForeignKey('department.id'))
...   name = Column('name', String(50))

在这里我们看到Course使用外键链接回其所在的部门

配置

我们需要实际解析这些对象来完全设置它们。通常grok会自动处理这个问题,但在这个例子中,我们将需要手动处理。

首先,我们解析这个包的解析器

>>> import grokcore.component.testing
>>> grokcore.component.testing.grok('megrok.rdb.meta')

现在我们可以解析组件

>>> from grokcore.component.testing import grok_component
>>> grok_component('Courses', Courses)
True
>>> grok_component('Department', Department)
True
>>> grok_component('Course', Course)
True

一旦我们定义了元数据和对象关系映射,我们还需要一个数据库来实际放置这些内容。虽然可以为每个Grok应用程序设置不同的数据库,但在这里我们将使用一个全局数据库

>>> TEST_DSN = 'sqlite:///:memory:'
>>> from z3c.saconfig import EngineFactory
>>> from z3c.saconfig.interfaces import IEngineFactory
>>> engine_factory = EngineFactory(TEST_DSN)

我们需要将引擎工厂作为实用程序提供。Grok可以使用模块级别的grok.global_utility指令为您自动执行此操作,如下所示

grok.global_utility(engine_factory, provides=IEngineFactory, direct=True)

在测试中,我们将直接使用组件架构

>>> from zope import component
>>> component.provideUtility(engine_factory, provides=IEngineFactory)

现在我们已经设置了一个引擎,我们可以设置SQLAlchemy会话实用程序

>>> from z3c.saconfig import GloballyScopedSession
>>> from z3c.saconfig.interfaces import IScopedSession
>>> scoped_session = GloballyScopedSession()

在Grok中,我们将这样注册它

grok.global_utility(scoped_session, provides=IScopedSession, direct=True)

但是,我们仍然会直接为测试注册它

>>> component.provideUtility(scoped_session, provides=IScopedSession)

我们现在需要创建我们在数据库中定义的表。我们只能在第一次创建引擎时执行此操作,因此我们为此设置了一个处理程序

>>> from z3c.saconfig.interfaces import IEngineCreatedEvent
>>> @component.adapter(IEngineCreatedEvent)
... def engine_created(event):
...    rdb.setupDatabase(metadata)
>>> component.provideHandler(engine_created)

使用数据库

现在我们已经处理完所有这些,我们可以使用rdb.Session对象与数据库建立连接。

>>> session = rdb.Session()

现在让我们创建一个数据库结构。我们有一个哲学系

>>> philosophy = Department(name="Philosophy")

我们需要手动将其添加到数据库中,因为我们没有在数据库中定义特定的departments容器

>>> session.add(philosophy)

哲学系有几个课程

>>> logic = Course(name="Logic")
>>> ethics = Course(name="Ethics")
>>> metaphysics = Course(name="Metaphysics")
>>> session.add_all([logic, ethics, metaphysics])

我们将把它们添加到哲学系的课程容器中。由于我们希望数据库来决定键,我们将使用特殊的方法 set,这是由 rdb.Container 对象拥有的,以便添加对象

>>> philosophy.courses.set(logic)
>>> philosophy.courses.set(ethics)
>>> philosophy.courses.set(metaphysics)

现在我们可以验证课程是否已存在

>>> for key, value in sorted(philosophy.courses.items()):
...     print key, value.name, value.department.name
1 Logic Philosophy
2 Ethics Philosophy
3 Metaphysics Philosophy

如你所见,自动生成的主键现在也用作容器键。

容器的键总是整数,即使我们处理的是主键

>>> philosophy.courses['1'].name
'Logic'

>>> philosophy.courses.get('1').name
'Logic'

使用 rdb.key 自定义键

现在,让我们设置一个不同的属性作为容器键。我们将使用课程的 name 属性。

我们将再次设置数据模型,这次在 Courses 类上使用 rdb.key

>>> metadata = rdb.MetaData()

>>> class Courses(rdb.Container):
...    rdb.key('name')

>>> class Department(rdb.Model):
...   rdb.metadata(metadata)
...
...   id = Column('id', Integer, primary_key=True)
...   name = Column('name', String(50))
...
...   courses = relation('Course',
...                       backref='department',
...                       collection_class=Courses)

>>> class Course(rdb.Model):
...   rdb.metadata(metadata)
...
...   id = Column('id', Integer, primary_key=True)
...   department_id = Column('department_id', Integer,
...                           ForeignKey('department.id'))
...   name = Column('name', String(50))

我们理解这些新类

>>> grok_component('Courses', Courses)
True
>>> grok_component('Department', Department)
True
>>> grok_component('Course', Course)
True

我们不需要更改引擎,因为底层的关系数据库保持不变。让我们设置另一个有某些系的学院

>>> physics = Department(name="Physics")
>>> session.add(physics)
>>> quantum = Course(name="Quantum Mechanics")
>>> relativity = Course(name="Relativity")
>>> high_energy = Course(name="High Energy")
>>> session.add_all([quantum, relativity, high_energy])

现在我们将这些系添加到物理学院

>>> physics.courses.set(quantum)
>>> physics.courses.set(relativity)
>>> physics.courses.set(high_energy)

现在我们可以验证课程是否已存在,名称作为键

>>> for key, value in sorted(physics.courses.items()):
...     print key, value.name, value.department.name
High Energy High Energy Physics
Quantum Mechanics Quantum Mechanics Physics
Relativity Relativity Physics

自定义查询容器

有时我们希望基于查询而不是关系将对象公开为(只读)容器。在构建应用程序时,这非常有用,你需要一个“起点”,一个启动到 SQLAlchemy 映射对象(该对象不是由 SQLAlchemy 直接管理的)的根对象。

我们可以通过从 rdb.QueryContainer 继承并实现特殊的 query 方法来构建这样的特殊容器。

>>> class MyQueryContainer(rdb.QueryContainer):
...   def query(self):
...      return session.query(Department)
>>> qc = MyQueryContainer()

让我们尝试一些常见的只读容器操作,例如 __getitem__

>>> qc['1'].name
u'Philosophy'
>>> qc['2'].name
'Physics'

FIXME 为什么“Philosophy”和“Physics”之间的 unicode 差异?

使用 KeyError__getitem__

>>> qc['3']
Traceback (most recent call last):
  ...
KeyError: '3'

get:

>>> qc.get('1').name
u'Philosophy'
>>> qc.get('3') is None
True
>>> qc.get('3', 'foo')
'foo'

__contains__:

>>> '1' in qc
True
>>> '3' in qc
False

has_key:

>>> qc.has_key('1')
True
>>> qc.has_key('3')
False

len:

>>> len(qc)
2

values:

>>> sorted([v.name for v in qc.values()])
[u'Philosophy', 'Physics']

所有值的父级是查询容器

>>> [v.__parent__ is qc for v in qc.values()]
[True, True]
>>> sorted([v.__name__ for v in qc.values()])
[u'1', u'2']

keys:

>>> sorted([key for key in qc.keys()])
[u'1', u'2']

items:

>>> sorted([(key, value.name) for (key, value) in qc.items()])
[(u'1', u'Philosophy'), (u'2', 'Physics')]

>>> [value.__parent__ is qc for (key, value) in qc.items()]
[True, True]
>>> sorted([value.__name__ for (key, value) in qc.items()])
[u'1', u'2']

__iter__:

>>> result = []
>>> for key in qc:
...   result.append(key)
>>> sorted(result)
[u'1', u'2']

转换 QueryContainer 的结果

有时在它们出现在容器中之前,将查询的结果转换为(或修改)其他东西非常有用。你可以实现 convert 方法来完成此操作。它接受由值返回的单独值,并应返回转换后的值。

>>> class ConvertingQueryContainer(rdb.QueryContainer):
...   def query(self):
...      return session.query(Department)
...   def convert(self, value):
...      return SpecialDepartment(value.id)

>>> class SpecialDepartment(object):
...    def __init__(self, id):
...      self.id = id

>>> qc = ConvertingQueryContainer()

现在让我们检查所有值都是 SpecialDepartment

>>> isinstance(qc['1'], SpecialDepartment)
True
>>> isinstance(qc['2'], SpecialDepartment)
True

KeyError 仍然有效

>>> qc['3']
Traceback (most recent call last):
  ...
KeyError: '3'

get:

>>> isinstance(qc.get('1'), SpecialDepartment)
True
>>> qc.get('3') is None
True
>>> qc.get('3', 'foo')
'foo'

values:

>>> [isinstance(v, SpecialDepartment) for v in qc.values()]
[True, True]

所有值的父级是查询容器

>>> [v.__parent__ is qc for v in qc.values()]
[True, True]
>>> sorted([v.__name__ for v in qc.values()])
[u'1', u'2']

items:

>>> sorted([(key, isinstance(value, SpecialDepartment)) for (key, value) in qc.items()])
[(u'1', True), (u'2', True)]

>>> [value.__parent__ is qc for (key, value) in qc.items()]
[True, True]
>>> sorted([value.__name__ for (key, value) in qc.items()])
[u'1', u'2']

进一步自定义 QueryContainer

有时定义自定义键func和从数据库检索键的自定义方法非常有用 - 这些通常一起实现。

>>> class KeyfuncQueryContainer(rdb.QueryContainer):
...   def query(self):
...      return session.query(Department)
...   def keyfunc(self, value):
...      return 'd' + unicode(value.id)
...   def dbget(self, key):
...      if not key.startswith('d'):
...          return None
...      return self.query().get(key[1:])

>>> qc = KeyfuncQueryContainer()
>>> qc.keys()
[u'd1', u'd2']
>>> qc[u'd1'].id
1

CHANGES

0.12 (2011-02-02)

  • 更新依赖关系和导入,以与 Grok 1.2 和 1.3 一起工作。

0.11 (2010-02-22)

  • 添加了 LICENSE.txt 文件。

  • 添加了 setupDatabaseSkipCreate。这允许设置数据库,而无需尝试创建任何表,只需反射。

0.10 (2009-09-18)

  • 添加到 SQLAlchemy 的 zope.schema 适配器,因此大多数 sqlalchemy.types 中的类型都得到了覆盖。

  • 将 schema_from_model 导入 megrok.rdb 包命名空间。

  • 更新 buildout 以使用 Grok 1.0b2 版本。

  • 添加了一个测试,该测试演示了在 IEngineCreatedEvent 订阅者中使用 rdb.setupDatabase 的常见初始化模式。

0.9.1 (2009-08-14)

  • megrok.rdb 0.9 错误地将 zip_safe 设置为 True,这导致其 ZCML 无法加载,从而发布了一个无效版本。将 zip_safe 设置为 False。

0.9 (2009-08-14)

  • 初始公开发布。

项目详情


下载文件

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

源代码发行版

megrok.rdb-0.12.tar.gz (21.4 kB 查看哈希值)

上传时间 源代码

由以下组织支持