基于SQLAlchemy的Grok RDB支持。
项目描述
megrok.rdb
简介
megrok.rdb包基于强大的SQLAlchemy库,为Grok添加了强大的关系数据库支持。它提供了新的megrok.rdb.Model和megrok.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 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 09387d5a55caa35f206886b06b50180e030d109d8514177024d0272dacc30735 |
|
MD5 | 1ea582759d7ce4ff8e6059565bf3f41e |
|
BLAKE2b-256 | 9616f7622de90588318777900c8ea483a8dbde6f7d2ddd1376524380d079af5b |