关于本文档
本文档介绍了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
查询在生成界面上是标准化的(旧界面仍然存在,仅此而已)。虽然大多数生成接口的可用性为0.3,但0.4 Query具有与外部生成匹配的内在胆量,并且有更多技巧。所有结果缩小都通过filter()
和filter_by()
,限制/偏移可以通过数组切片或limit()
/ offset()
,加入是通过join()
和outerjoin()
(或者更手动地通过select_from()
作为手动形成的标准)。
为避免弃用警告,您必须对您的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
仍然用于访问常规表中的列和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')
以上将使用别名从订单 - >项目创建两个连接。每个后面的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()
对于在flush()
期间嵌入式执行SQL子句,直接嵌入UPDATE或INSERT中:
myobject.foo = mytable.c.value + 1
user.pwhash = func.md5(password)
order.hash = text("select hash from hashing_table")
在操作之后,使用延迟加载器设置column-attribute,以便在下次访问时发出SQL以加载新值。
由于我们的alias-fu已经改进,所以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
时,任何类型的热切加载链都可以循环回自身。当不存在时,急切加载在碰到一个循环时自动停止。
这是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()
将一组“延迟”列标记为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()
集合不再由{{{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
样和自由格式的对象派生集合类。在大多数情况下,这只是将一些装饰器添加到类定义中的问题。
这个特性静静地出现在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
[browser:/ sqlalchemy / trunk / examples / sharding / attribute_shard .py]
没错,整个shebang被两个配置函数取代。使用它们将产生自0.1以来我们已经具有的最多0.1sh的感觉(即,最少量的打字)。
在您定义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()
在引擎和ORM级别可用。ORM文档到目前为止:
http://www.sqlalchemy.org/docs/04/session.html#unitofwork_ma naging
新的继承文档:http://www.sqlalchemy.org/docs/04 /mappers.html#advdatamapping_mapper_inheritance_joined
get()
¶更好地进行多态行为连接表继承层次结构中的所有类都使用基类获取_instance_key
,即(BaseClass, (1, ), 无)
。通过这种方式,当您针对基类调用get()
a Query
时,可以在当前标识映射中查找子类实例,而无需查询数据库。
所有“匿名”标签和别名现在都使用简单的
这绝对是通过select()
进行的。请参阅htt p://www.sqlalchemy.org/docs/04/sqlexpression.html#sql_transf orm。
SQL运算符和或多或少每个SQL关键字都被抽象到编译器层。他们现在可以智能地操作,并且可以识别类型/后端,请参阅:http://www.sq lalchemy.org/docs/04/sqlexpression.html#sql_operators
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系列中,不赞成使用MetaData
和ThreadLocalMetaData
的BoundMetaData
和DynamicMetaData
。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) | |
+-------------------------------------+-------------------------+
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.
engine
, connectable
, and bind_to
are all now bind
¶Transactions
,NestedTransactions
和TwoPhaseTransactions
¶连接池现在会在创建新的DB-API连接时触发事件,检出并检入池中。例如,您可以使用它们在新连接上执行会话范围的SQL安装语句。
在0.3.11中,Oracle引擎中存在关于如何处理主键的错误。这些错误可能会导致在其他引擎(如sqlite)中正常工作的程序在使用Oracle引擎时失败。在0.4版本中,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}
MetaData
,Sessions
¶MetaData
and Session
can be explicitly bound to a connection:
conn = engine.connect()
sess = create_session(bind=conn)
ResultProxy
对象¶