版本:1.1.0b2 |发布日期:2016年7月1日

SQLAlchemy 1.1文档

更改和迁移

项目版本

SQLAlchemy 0.4中有什么新东西?

关于本文档

本文档介绍了2008年10月14日发布的SQLAlchemy 0.3版和2007年10月12日发布的SQLAlchemy 0.4版之间的变化。

文件日期:2008年3月21日

第一件事第一件

如果您使用任何ORM功能,请确保从sqlalchemy.orm导入:

from sqlalchemy import *
from sqlalchemy.orm import *

Secondly, anywhere you used to say engine=, connectable=, bind_to=, something.engine, metadata.connect(), use 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,则可以立即进行这些更改;他们也会在那里工作

模块导入

In 0.3, “from sqlachemy import *” would import all of sqlachemy’s sub-modules into your namespace. 版本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

对象关系映射

查询¶ T0>

新的查询API

查询在生成界面上是标准化的(旧界面仍然存在,不赞成使用)。虽然大多数的生成接口在0.3中可用,但是0.4 Query具有与外部生成匹配的内在的胆量,并且具有更多的技巧。All result narrowing is via filter() and filter_by(), limiting/offset is either through array slices or limit()/offset(), joining is via join() and outerjoin() (or more manually, through select_from() as well as manually-formed criteria).

为避免弃用警告,您必须对03代码进行一些更改

User.query.get_by(** kwargs)

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

User.query.select_by(** kwargs)

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 is still used to access columns in regular tables and selectable objects produced from SQL Expressions.

自动连接别名

我们已经有一段时间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')

以上将使用别名从订单 - >项目创建两个连接。每个后面的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()

关系¶ T0>

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

对于嵌套执行SQL子句,直接嵌入在UPDATE或INSERT中,在flush()中:

myobject.foo = mytable.c.value + 1

user.pwhash = func.md5(password)

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

在操作之后,使用延迟加载器设置column-attribute,以便在下次访问时发出SQL以加载新值。

自引用和周期性快速加载

Since our alias-fu has improved, relation() can join along the same table *any number of times*; you tell it how deep you want to go. 让我们更清楚地显示自引用的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时,任何类型的热切加载链都可以循环回自己。当不存在时,急切的加载在碰到一个循环时自动停止。

复合类型

这是来自Hibernate阵营的。复合类型使您可以定义由多个列(或者一列)组成的自定义数据类型(如果需要)。让我们定义一个新的类型,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),
    )

然后,映射它!我们将创建一个存储两个Point对象的Vertex对象:

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()关系

一个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() marks a whole group of “deferred” columns as undeferred:

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

集合不再由{{{InstrumentedList}}}代理代理,并且对成员,方法和属性的访问是直接的。装饰者现在拦截进入和离开集合的对象,现在可以轻松地编写一个自定义集合类来管理自己的成员资格。灵活的装饰器也替换了0.3中定制集合的命名方法接口,允许任何类很容易地被用作集合容器。

基于字典的集合现在更容易使用,并且完全像dict一样。Changing __iter__ is no longer needed for dict``s, and new built-in ``dict types cover many needs:

# 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))

现在需要为新的API更新0.3 dict -like和自由形式的对象派生集合类。在大多数情况下,这只是将一些装饰器添加到类定义的问题。

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

这个特性静静地出现在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]

会话¶ T0>

新会话创建范式; SessionContext,assignmapper已过时

没错,整个文件被两个配置函数取代。使用这两者将产生自0.1以来我们已经具有的最多0.1分的感觉(即,最少量的打字)。

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

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(...))

All the behaviors of SessionContext and the query and __init__ methods of assignmapper are moved into the new scoped_session() function, which is compatible with both sessionmaker as well as 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接口实现为classmethods,而“assignmapper”的功能可以使用mapper就像旧的objectstore天...

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

u = User(name='wendy')

Session.commit()

会话再次默认为弱引用

在Session上,weak_identity_map标志现在默认设置为True外部推断和超出范围的实例将自动从会话中删除。然而,具有“脏”变化的项目将保持强烈引用,直到这些变化被刷新为止,在这种情况下,对象恢复为弱引用(这适用于“可变”类型,如可选属性)。将weak_identity_map设置为False将使用会话(例如缓存)恢复旧的强引用行为。

自动事务会话

正如您可能已经注意到的,我们正在Session上调用commit()标志transactional=True表示Session总是在事务中,commit()永久保存。

自动刷新会话

Also, autoflush=True means the Session will flush() before each query as well as when you call flush() or 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

与封闭的引擎级(即非ORM)事务共享Session很容易:

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()

使用SAVEPOINT 嵌套会话事务

在引擎和ORM级别可用。ORM文档到目前为止:

http://www.sqlalchemy.org/docs/04/session.html#unitofwork_ma naging

两阶段提交会话

在引擎和ORM级别可用。ORM文档到目前为止:

http://www.sqlalchemy.org/docs/04/session.html#unitofwork_ma naging

继承¶ T0>

没有联盟或联盟的多态继承

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

使用get() 更好的多态行为

All classes within a joined-table inheritance hierarchy get an _instance_key using the base class, i.e. (BaseClass, (1, ), None). 通过这种方式,当针对基类调用get() a Query时,可以查找当前标识映射中的子类实例,而无需查询数据库。

类型¶ T0>

sqlalchemy.types.TypeDecorator

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

SQL表达式

全新,确定性标签/别名生成

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

Generative select()构造

这肯定是用select()的方法。请参阅https://www.sqlalchemy.org/docs/04/sqlexpression.html#sql_transf orm。

新的操作员系统

SQL运算符和或多或少每个SQL关键字都被抽象到编译器层。他们现在聪明地行事,并且知道类型/后端,请参阅:http://www.sq lalchemy.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)

模式和反思

MetaData, BoundMetaData, DynamicMetaData...

在0.3.x系列中,不赞成使用MetaDataThreadLocalMetaDataBoundMetaDataDynamicMetaData0.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) |                         |
+-------------------------------------+-------------------------+

MetaData类型的很少使用的name参数已被删除。ThreadLocalMetaData构造函数现在不带任何参数。现在,这两种类型都可以绑定到Engine或单个Connection

一步多表反射

您现在可以加载表定义,并自动在整个数据库或模式中一次性创建Table对象:

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

MetaData also gains a .reflect() method enabling finer control over the loading process, including specification of a subset of available tables to load.

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的输出参数

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

MetaData and Session can be explicitly bound to a connection:

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

更快,更安全ResultProxy对象