SQLAlchemy 0.4有什么新功能?

优质
小牛编辑
130浏览
2023-12-01

关于此文档

本文档描述了上次发布于2007年10月14日的SQLAlchemy版本0.3和上次发布于2008年10月12日的SQLAlchemy版本0.4之间的更改。

文件日期:2008年3月21日

第一件事

如果您使用的是任何ORM功能,请确保从 sqlalchemy.orm

from sqlalchemy import *
from sqlalchemy.orm import *

第二,你以前说的任何地方 engine=connectable=bind_to=something.enginemetadata.connect() 使用 bind

myengine = create_engine('sqlite://')

meta = MetaData(myengine)

meta2 = MetaData()
meta2.bind = myengine

session = create_session(bind=myengine)

statement = select([table], bind=myengine)

明白了吗?好!您现在(95%)与0.4兼容。如果您使用的是0.3.10,您可以立即进行这些更改;它们也将在那里工作。

模块导入

从sqlhemy*的所有模块导入到sqlhemy的所有名称空间。版本0.4不再将子模块导入命名空间。您可能需要在代码中添加额外的导入。

在0.3中,此代码起作用:

from sqlalchemy import *

class UTCDateTime(types.TypeDecorator):
    pass

在0.4中,必须做到:

from sqlalchemy import *
from sqlalchemy import types

class UTCDateTime(types.TypeDecorator):
    pass

对象关系映射

查询

新的查询API

查询在生成接口上是标准化的(旧接口仍然存在,只是不推荐使用)。虽然大多数生成接口都在0.3中可用,但0.4查询具有与外部生成接口匹配的内部勇气,并且具有更多的技巧。所有结果缩小都是通过 filter() and filter_by(), limiting/offset is either through array slices or limit()/offset() ,加入是通过 join()outerjoin() (或更多手动,通过 select_from() 以及人工形成的标准)。

为避免出现拒绝警告,必须对03代码进行一些更改

用户.查询.获取( * *克沃斯)

User.query.filter_by(**kwargs).first()

user.query.选择( * *克沃斯)

User.query.filter_by(**kwargs).all()

user.query.select()。

User.query.filter(xxx).all()

新的基于属性的表达式构造

到目前为止,ORM中最明显的区别是,现在可以直接使用基于类的属性构造查询条件。使用映射类时不再需要“.c.”前缀:

session.query(User).filter(and_(User.name == 'fred', User.id > 17))

虽然简单的基于列的比较没有什么大不了的,但是类属性有一些新的“更高级别”构造可用,包括以前只在 filter_by()

# comparison of scalar relations to an instance
filter(Address.user == user)

# return all users who contain a particular address
filter(User.addresses.contains(address))

# return all users who *dont* contain the address
filter(~User.address.contains(address))

# return all users who contain a particular address with
# the email_address like '%foo%'
filter(User.addresses.any(Address.email_address.like('%foo%')))

# same, email address equals 'foo@bar.com'.  can fall back to keyword
# args for simple comparisons
filter(User.addresses.any(email_address = 'foo@bar.com'))

# return all Addresses whose user attribute has the username 'ed'
filter(Address.user.has(name='ed'))

# return all Addresses whose user attribute has the username 'ed'
# and an id > 5 (mixing clauses with kwargs)
filter(Address.user.has(User.id > 5, name='ed'))

这个 Column 集合在中的映射类上仍然可用 .c 属性。请注意,基于属性的表达式仅对映射类的映射属性可用。 .c 仍然用于访问规则表中的列以及从SQL表达式生成的可选对象。

自动连接别名

我们已经有join()和outerjoin()一段时间了:

session.query(Order).join('items')...

现在您可以将它们命名为:

session.query(Order).join('items', aliased=True).
   filter(Item.name='item 1').join('items', aliased=True).filter(Item.name=='item 3')

上面将使用别名从orders->items创建两个联接。这个 filter() 每个调用之后的调用将其表条件调整为别名的表条件。到达 Item 使用对象 add_entity() 并以每个连接为目标 id

session.query(Order).join('items', id='j1', aliased=True).
filter(Item.name == 'item 1').join('items', aliased=True, id='j2').
filter(Item.name == 'item 3').add_entity(Item, id='j1').add_entity(Item, id='j2')

返回以下形式的元组: (Order, Item, Item) .

自引用查询

所以query.join()现在可以生成别名。这给了我们什么?自引用查询!连接不需要任何 Alias 物体:

# standard self-referential TreeNode mapper with backref
mapper(TreeNode, tree_nodes, properties={
    'children':relation(TreeNode, backref=backref('parent', remote_side=tree_nodes.id))
})

# query for node with child containing "bar" two levels deep
session.query(TreeNode).join(["children", "children"], aliased=True).filter_by(name='bar')

要在别名联接中为每个表添加条件,可以使用 from_joinpoint 要根据相同的别名行继续连接,请执行以下操作:

# search for the treenode along the path "n1/n12/n122"

# first find a Node with name="n122"
q = sess.query(Node).filter_by(name='n122')

# then join to parent with "n12"
q = q.join('parent', aliased=True).filter_by(name='n12')

# join again to the next parent with 'n1'.  use 'from_joinpoint'
# so we join from the previous point, instead of joining off the
# root table
q = q.join('parent', aliased=True, from_joinpoint=True).filter_by(name='n1')

node = q.first()

query.populate_existing()

渴望的版本 query.load() (或) session.refresh() )从查询加载的每个实例,包括所有热切加载的项,如果会话中已经存在,则立即刷新:

session.query(Blah).populate_existing().all()

关系

在更新/插入中嵌入的SQL子句

对于SQL子句的内联执行,在 flush()

myobject.foo = mytable.c.value + 1

user.pwhash = func.md5(password)

order.hash = text("select hash from hashing_table")

该列属性在操作后使用延迟加载程序设置,以便在下次访问时发出SQL来加载新值。

自参考和周期性预加载

既然我们的化名傅已经改进了, relation() 可以沿同一个表联接 * 任何次数*;你告诉它你想走多远。让我们展示自我参照 TreeNode 更清楚地说:

nodes = Table('nodes', metadata,
     Column('id', Integer, primary_key=True),
     Column('parent_id', Integer, ForeignKey('nodes.id')),
     Column('name', String(30)))

class TreeNode(object):
    pass

mapper(TreeNode, nodes, properties={
    'children':relation(TreeNode, lazy=False, join_depth=3)
})

当我们说:

create_session().query(TreeNode).all()

?沿别名连接,离父级有三个级别:

SELECT
nodes_3.id AS nodes_3_id, nodes_3.parent_id AS nodes_3_parent_id, nodes_3.name AS nodes_3_name,
nodes_2.id AS nodes_2_id, nodes_2.parent_id AS nodes_2_parent_id, nodes_2.name AS nodes_2_name,
nodes_1.id AS nodes_1_id, nodes_1.parent_id AS nodes_1_parent_id, nodes_1.name AS nodes_1_name,
nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id, nodes.name AS nodes_name
FROM nodes LEFT OUTER JOIN nodes AS nodes_1 ON nodes.id = nodes_1.parent_id
LEFT OUTER JOIN nodes AS nodes_2 ON nodes_1.id = nodes_2.parent_id
LEFT OUTER JOIN nodes AS nodes_3 ON nodes_2.id = nodes_3.parent_id
ORDER BY nodes.oid, nodes_1.oid, nodes_2.oid, nodes_3.oid

请注意,别名也很干净。连接并不关心它是针对同一个即时表还是其他对象,这些对象随后循环回到开始。任何一种热切的负载链在 join_depth 指定。当不存在时,当到达循环时,预加载自动停止。

复合类型

这是冬眠营的一个。复合类型允许您定义由多个列(如果需要,也可以定义一个列)组成的自定义数据类型。让我们定义一个新类型, Point . 存储X/Y坐标:

class Point(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __composite_values__(self):
        return self.x, self.y
    def __eq__(self, other):
        return other.x == self.x and other.y == self.y
    def __ne__(self, other):
        return not self.__eq__(other)

方式 Point 对象是特定于自定义类型的;构造函数接受参数列表,并且 __composite_values__() 方法生成这些参数的序列。订单将与我们的映射器匹配,稍后我们将看到。

让我们创建一个顶点表,每行存储两个点:

vertices = Table('vertices', metadata,
    Column('id', Integer, primary_key=True),
    Column('x1', Integer),
    Column('y1', Integer),
    Column('x2', Integer),
    Column('y2', Integer),
    )

然后,绘制地图!我们将创造一个 Vertex 存储两个 Point 物体:

class Vertex(object):
    def __init__(self, start, end):
        self.start = start
        self.end = end

mapper(Vertex, vertices, properties={
    'start':composite(Point, vertices.c.x1, vertices.c.y1),
    'end':composite(Point, vertices.c.x2, vertices.c.y2)
})

一旦设置了复合类型,它就可以像其他类型一样使用:

v = Vertex(Point(3, 4), Point(26,15))
session.save(v)
session.flush()

# works in queries too
q = session.query(Vertex).filter(Vertex.start == Point(3, 4))

如果要定义在表达式中使用映射属性生成SQL子句的方式,请创建自己的 sqlalchemy.orm.PropComparator 子类,定义任何公共运算符(如 __eq__()__le__() 等等),然后发送到 composite() . 复合类型也可用作主键,并可用于 query.get()

# a Document class which uses a composite Version
# object as primary key
document = query.get(Version(1, 'a'))

dynamic_loader() 关系

A relation() 返回实况 Query 所有读取操作的对象。写操作仅限于 append()remove() ,在刷新会话之前,对集合的更改不可见。这个特性对于“自动刷新”会话特别方便,它将在每次查询之前刷新。

mapper(Foo, foo_table, properties={
    'bars':dynamic_loader(Bar, backref='foo', <other relation() opts>)
})

session = create_session(autoflush=True)
foo = session.query(Foo).first()

foo.bars.append(Bar(name='lala'))

for bar in foo.bars.filter(Bar.name=='lala'):
    print(bar)

session.commit()

新选项: undefer_group()eagerload_all()

几个方便的查询选项。 undefer_group() 将整个“延迟”列组标记为未检索:

mapper(Class, table, properties={
    'foo' : deferred(table.c.foo, group='group1'),
    'bar' : deferred(table.c.bar, group='group1'),
    'bat' : deferred(table.c.bat, group='group1'),
)

session.query(Class).options(undefer_group('group1')).filter(...).all()

eagerload_all() 设置一个属性链,使其在一次传递中变得更为重要:

mapper(Foo, foo_table, properties={
   'bar':relation(Bar)
})
mapper(Bar, bar_table, properties={
   'bat':relation(Bat)
})
mapper(Bat, bat_table)

# eager load bar and bat
session.query(Foo).options(eagerload_all('bar.bat')).filter(...).all()

新集合API

集合不再由instructedList代理代理,对成员、方法和属性的访问是直接的。装饰器现在拦截进入和离开集合的对象,现在可以轻松地编写一个自定义集合类来管理它自己的成员资格。灵活的装饰器还替换了0.3中自定义集合的命名方法接口,允许轻松地将任何类调整为用作集合容器。

基于词典的集合现在更易于使用和充分利用。 dict -喜欢。改变 __iter__ 不再需要 dict 和新的内置 dict 类型可满足多种需求:

# use a dictionary relation keyed by a column
relation(Item, collection_class=column_mapped_collection(items.c.keyword))
# or named attribute
relation(Item, collection_class=attribute_mapped_collection('keyword'))
# or any function you like
relation(Item, collection_class=mapped_collection(lambda entity: entity.a + entity.b))

现有0.3 dict -Like和Freeform对象派生的集合类需要为新的API更新。在大多数情况下,这仅仅是在类定义中添加几个修饰符的问题。

从外部表/子查询映射的关系

此功能在0.3中悄然出现,但在0.4中得到了改进,因为它能够更好地将针对表的子查询转换为针对该表的别名的子查询;这是急加载、查询中的别名联接等的关键。当您只需要添加一些额外的列时,它减少了针对select语句创建映射器的需要。或子查询:

mapper(User, users, properties={
       'fullname': column_property((users.c.firstname + users.c.lastname).label('fullname')),
       'numposts': column_property(
            select([func.count(1)], users.c.id==posts.c.user_id).correlate(users).label('posts')
       )
    })

典型的查询如下:

SELECT (SELECT count(1) FROM posts WHERE users.id = posts.user_id) AS count,
users.firstname || users.lastname AS fullname,
users.id AS users_id, users.firstname AS users_firstname, users.lastname AS users_lastname
FROM users ORDER BY users.oid

水平缩放(切分)API

[browser:/sqlalchemy/trunk/examples/sharding/attribute_shard .py]

会议

新建会话创建范例;sessionContext,assignmapper已弃用

没错,整个shebang被两个构型函数取代了。使用这两种方法将产生自0.1以来我们拥有的最0.1-ish的感觉(即,输入量最少)。

配置您自己的 Session 在定义您的 engine (或任何地方):

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine('myengine://')
Session = sessionmaker(bind=engine, autoflush=True, transactional=True)

# use the new Session() freely
sess = Session()
sess.save(someobject)
sess.flush()

如果需要对会话进行后期配置,例如使用引擎,请稍后使用 configure()

Session.configure(bind=create_engine(...))

所有的行为 SessionContext 以及 query__init__ 方法 assignmapper 被移到新的 scoped_session() 功能,两者都兼容 sessionmaker 以及 create_session()

from sqlalchemy.orm import scoped_session, sessionmaker

Session = scoped_session(sessionmaker(autoflush=True, transactional=True))
Session.configure(bind=engine)

u = User(name='wendy')

sess = Session()
sess.save(u)
sess.commit()

# Session constructor is thread-locally scoped.  Everyone gets the same
# Session in the thread when scope="thread".
sess2 = Session()
assert sess is sess2

当使用本地线程时 Session ,返回的类具有 Session's 接口作为类方法实现,并且“assignmapper”的功能可以使用 mapper 类方法。就像以前一样 objectstore 天。。。。

# "assignmapper"-like functionality available via ScopedSession.mapper
Session.mapper(User, users_table)

u = User(name='wendy')

Session.commit()

会话在默认情况下再次弱引用

弱标识映射标志现在设置为 True 默认为会话。外部延迟和超出范围的实例将自动从会话中删除。但是,存在“脏”更改的项将保持强引用状态,直到刷新这些更改,在这种情况下,对象将恢复为弱引用状态(这同样适用于“可变”类型,如可拾取属性)。将弱身份映射设置为 False 为使用会话的用户(如缓存)还原旧的强引用行为。

自动事务会话

你可能已经注意到了,我们打电话来 commit()Session . 旗 transactional=True 意味着 Session 总是在事务中, commit() 永久存在。

自动刷新会话

也, autoflush=True 意味着 Sessionflush() 在每个之前 query 以及你打电话的时候 flush()commit() . 因此,现在这将起作用:

Session = sessionmaker(bind=engine, autoflush=True, transactional=True)

u = User(name='wendy')

sess = Session()
sess.save(u)

# wendy is flushed, comes right back from a query
wendy = sess.query(User).filter_by(name='wendy').one()

事务方法已转移到会话中

commit()rollback() 以及 begin() 现在直接打开 Session . 不再需要使用 SessionTransaction 为了任何事情(它仍然在后台)。

Session = sessionmaker(autoflush=True, transactional=False)

sess = Session()
sess.begin()

# use the session

sess.commit() # commit transaction

共享一个 Session 使用封闭引擎级别(即非ORM)事务很容易:

Session = sessionmaker(autoflush=True, transactional=False)

conn = engine.connect()
trans = conn.begin()
sess = Session(bind=conn)

# ... session is transactional

# commit the outermost transaction
trans.commit()

具有保存点的嵌套会话事务

可在发动机和ORM级别使用。迄今为止的ORM文档:

https://www.sqlalchemy.org/docs/04/session.html

两阶段提交会话

可在发动机和ORM级别使用。迄今为止的ORM文档:

https://www.sqlalchemy.org/docs/04/session.html

遗传

无连接或联合的多态继承

继承的新文档:https://www.sqlalchemy.org/docs/04/mappers.html

具有更好的多态性 get()

联接表继承层次结构中的所有类 _instance_key 使用基类,即 (BaseClass, (1, ), None) . 当你打电话的时候 get()Query 对于基类,它可以在当前标识映射中定位子类实例,而无需查询数据库。

类型

自定义子类 sqlalchemy.types.TypeDecorator

有一个 New API 用于将TypeDecorator子类化。在某些情况下,使用0.3API会导致编译错误。

SQL表达式

所有新的、确定性的标签/别名生成

现在,所有“匿名”标签和别名都使用简单的<name>_<number>格式。SQL更易于阅读,并且与计划优化器缓存兼容。只需查看教程中的一些示例:https://www.sqlalchemy.org/docs/04/ormtutorial.html https://www.sqlalchemy.org/docs/04/sqlexpression.html

generative select()构造

这绝对是一种方法 select() . 请参见htt p://www.sqlachemy.org/docs/04/sqlexpression.html_sql_transf orm。

新操作员系统

SQL运算符和或多或少的每个SQL关键字现在都抽象到了编译器层中。他们现在可以智能操作,并且可以识别类型/后端,请参阅:https://www.sqlalchemy.org/docs/04/sqlexpression.html#sql_operators

所有 type 关键字参数重命名为 type_

就像上面说的:

b = bindparam('foo', type_=String)

in_ 功能更改为接受序列或可选

这个 in_ 函数现在接受值序列或可选参数作为其唯一参数。以前将值作为位置参数传入的API仍然有效,但现在已弃用。这意味着

my_table.select(my_table.c.id.in_(1,2,3)
my_table.select(my_table.c.id.in_(*listOfIds)

应改为

my_table.select(my_table.c.id.in_([1,2,3])
my_table.select(my_table.c.id.in_(listOfIds)

模式和反射

MetaDataBoundMetaDataDynamicMetaData

在0.3.x系列中, BoundMetaDataDynamicMetaData 被否决,赞成 MetaDataThreadLocalMetaData . 旧名称已在0.4中删除。更新很简单:

+-------------------------------------+-------------------------+
|If You Had                           | Now Use                 |
+=====================================+=========================+
| ``MetaData``                        | ``MetaData``            |
+-------------------------------------+-------------------------+
| ``BoundMetaData``                   | ``MetaData``            |
+-------------------------------------+-------------------------+
| ``DynamicMetaData`` (with one       | ``MetaData``            |
| engine or threadlocal=False)        |                         |
+-------------------------------------+-------------------------+
| ``DynamicMetaData``                 | ``ThreadLocalMetaData`` |
| (with different engines per thread) |                         |
+-------------------------------------+-------------------------+

很少使用 name 参数到 MetaData 类型已被删除。这个 ThreadLocalMetaData 构造函数现在不接受参数。这两种类型现在都可以绑定到 Engine 或者一个 Connection .

一步多表反射

现在可以加载表定义并自动创建 Table 一次传递来自整个数据库或架构的对象:

>>> metadata = MetaData(myengine, reflect=True)
>>> metadata.tables.keys()
['table_a', 'table_b', 'table_c', '...']

MetaData 还得A .reflect() 方法可以更好地控制加载过程,包括指定要加载的可用表的子集。

SQL执行

engine, connectable, and bind_to are all now bind

Transactions, NestedTransactions and TwoPhaseTransactions

连接池事件

连接池现在在创建、签出和签回新的DB-API连接时触发事件。例如,可以使用这些语句在新连接上执行会话范围的SQL安装语句。

Oracle引擎已修复

在0.3.11中,Oracle引擎中有一些关于如何处理主键的错误。这些错误可能会导致与其他引擎(如sqlite)一起工作良好的程序在使用Oracle引擎时失败。在0.4中,Oracle引擎被重新设计,解决了这些主要的关键问题。

Oracle的out参数

result = engine.execute(text("begin foo(:x, :y, :z); end;", bindparams=[bindparam('x', Numeric), outparam('y', Numeric), outparam('z', Numeric)]), x=5)
assert result.out_parameters == {'y':10, 'z':75}

连接绑定 MetaDataSessions

MetaDataSession 可以显式绑定到连接:

conn = engine.connect()
sess = create_session(bind=conn)

更快,更简单 ResultProxy 物体