博客版本: 0.1 Initial Release.
Prerequisites(Very Important!!!):
- 熟悉SQLAlchemy常用用法, 一個(gè)很好的教程來(lái)自于SQLAlchmey作者M(jìn)ike Bayer在PyCon13的Tutorial.
- 熟悉Flask官方文檔的關(guān)于如何寫一個(gè)SQLite3的Flask Extension的示例.
- 熟悉Python的一些高級(jí)特性:描述符,metaclass.
- init和new的區(qū)別.
- 對(duì)數(shù)據(jù)庫(kù)有一定了解颂碧,推薦Stanford CS145的數(shù)據(jù)庫(kù)入門課程.
Goals:
- 學(xué)習(xí)Flask的插件是怎么工作的.
- 增加對(duì)SQLAlchemy的認(rèn)識(shí).
- 學(xué)習(xí)Flask中teardown_appcontext的用法.
Table of Contents
Part1 定義一張表
Part2 創(chuàng)建一張表
Part3 查詢一張表
Part4 miscellanies
Part1 定義一張表
天才第一步南吮,讓我們先從定義一個(gè)表開始吧.
在SQLAlchemy中,以O(shè)RM方式定義表有兩種方法,分別是Classical vs. Declarative.
Flask-SQLAlchemy主要使用的是Declarative方法.
首先, 我們回顧一下在原生SQLAlchemy中是如何進(jìn)行Declarative定義的。
# 1.py
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
from sqlalchemy import Column, Integer, String
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
username = Column(String(50))
email = Column(String(30))
def __repr__(self):
return "<User(%r, %r)>" % self.username
Declarative方法本質(zhì)上就是自動(dòng)完成了Classical方法所做的事情.所謂Declarative方式的意思就是說(shuō)什么就有什么的方式,
比如,要有光,就有光,定義完一個(gè)User類次员,要有Table表,于是也就在Metadata中有了Table表.
當(dāng)User定義完成時(shí)王带,User class現(xiàn)在就有了一個(gè)Table object.可以通過(guò)User.table或者
User.metadata.tables['user']來(lái)訪問(wèn).The Declarative extension uses a Python metaclass, which is a handy way to run a series of operations each time a new class is first declared, to generate a new Table object from what's been declared, and pass it to the mapper funcion along with the class.
在Flask-SQLAlchemy中淑蔚,Model是對(duì)SQLAlchemy的Base的包裝,
在Flask-SQLAlchemy中,我們將Base改為db.Model.
# 2.py
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120), unique=True)
def __init__(self, username, email):
self.username = username
self.email = email
def __repr__(self):
return '<User %r>' % self.username
下面我們來(lái)看看db.Model:
# 3.py
self.Model = self.make_declarative_base(metadata)
...
def make_declarative_base(self, metadata=None):
"""Creates the declarative base."""
base = declarative_base(cls=Model, name='Model',
metadata=metadata,
metaclass=_BoundDeclarativeMeta)
base.query = _QueryProperty(self)
return base
在SQLAlchemy的官方文檔中查詢declarative_base的API.
# SQLAlchemy declarative_base() API.
def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
name='Base', constructor=_declarative_constructor,
class_registry=None,
metaclass=DeclarativeMeta):
可知各個(gè)參數(shù)的意義:
*1. bind - An optional Connectable, will be assigned the bind attribute on the MetaData instance.
這里的Connectable這個(gè)接口的實(shí)現(xiàn)有:Engine和Connection.
*2. metadata - All Table objects implicitly declared by subclasses of the base will share this
MetaData.
*3. mapper - Will be used to map subclasses to their Tables.
*4. cls - A type to use as the base for the generated declarative base class.
*5. name - The display name for the generated class.
*6. constructor - ?????
*7. class_registry - ?????
*8. metaclass - A metaclass or metaclass compatible callable to use as the meta type of the
meta type of the generated declarative base class.
首先metaclass(創(chuàng)造class的class)默認(rèn)設(shè)置為DeclarativeMeta辫秧,這是最重要的設(shè)置束倍,
DeclarativeMeta具體我們不會(huì)去剖析被丧,但是根據(jù)metaclass的用途盟戏,我們可以大膽猜測(cè),
定義類User時(shí)甥桂,在metaclass創(chuàng)造User這個(gè)class時(shí)柿究,會(huì)做很多背后的設(shè)置工作,具體做些什么呢:
The Declarative extension uses a Python metaclass, which is a
handy way to run a series of operations each time a new class is first declared, to generate a
new Table object from what's been declared, and pass it to the mapper funcion along with the
class.
在3.py中黄选,我們?cè)O(shè)置declarative_base的參數(shù)有:
- cls = Model
- metadata = metadata(None)
- metaclass = _BoundDeclarativeMeta
_BoundDeclarativeMeta是對(duì)DeclarativeMeta的一個(gè)簡(jiǎn)單包裝:
# 4.py
class _BoundDeclarativeMeta(DeclarativeMeta):
def __new__(cls, name, bases, d):
if _should_set_tablename(bases, d):
def _join(match):
word = match.group()
if len(word) > 1:
return ('_%s_%s' % (word[:-1], word[-1])).lower()
return '_' + word.lower()
d['__tablename__'] = _camelcase_re.sub(_join, name).lstrip('_')
return DeclarativeMeta.__new__(cls, name, bases, d)
def __init__(self, name, bases, d):
bind_key = d.pop('__bind_key__', None)
DeclarativeMeta.__init__(self, name, bases, d)
if bind_key is not None:
self.__table__.info['bind_key'] = bind_key
_BoundDeclarativeMeta實(shí)現(xiàn)的功能就是自動(dòng)添加tablename屬性的設(shè)置, 在SQLAlchemy中蝇摸,
比如在1.py中,tablename的設(shè)置是必須的办陷,但是在2.py這項(xiàng)并不是必須貌夕,原因就是Flask-SQLAlchemy
在背后幫你自動(dòng)設(shè)置好了.
首先我們思考一個(gè)問(wèn)題在2.py中,在定義(創(chuàng)造民镜?)User這個(gè)class過(guò)程中啡专,
傳入new中的參數(shù)會(huì)是哪些呢?設(shè)置打印語(yǔ)句可知:
cls ---> <class 'flask_sqlalchemy._BoundDeclarativeMeta'>
name ---> User
bases ---> (<class 'flask_sqlalchemy.Model'>,)
d ---> {'module': ..., 'init': ..., 'id':..., 'qualname': ...,
'username': ..., 'email': ..., 'repr':...}
下面我們看 _should_set_tablename干了啥:
def _should_set_tablename(bases, d):
# 如果d中制圈,也就是User.__dict__中含有這些屬性们童,則不用自動(dòng)設(shè)置.
if '__tablename__' in d or '__table__' in d or '__abstract__' in d:
return False
# 檢查User中的Column屬性,如果任何一個(gè)Column屬性為primary_key,則返回True.
if any(v.primary_key for v in itervalues(d) if isinstance(v, sqlalchemy.Column)):
return True
# Joined table inheritance without explicitly naming sub-models.?????
for base in bases:
if hasattr(base, '__tablename__') or hasattr(base, '__table__'):
return False
for name in dir(base):
attr = getattr(base, name)
if isinstance(attr, sqlalchemy.Column) and attr.primary_key:
return True
如果 _should_set_tablename返回True鲸鹦,我們就會(huì)設(shè)置d['tablename'],設(shè)置的具體格式就忽略.
設(shè)置完后慧库,我們?cè)僬{(diào)用SQLAlchemy Base默認(rèn)的metaclass的new方法.
_BoundDeclarativeMeta在init中還對(duì)bind_key進(jìn)行了設(shè)置,這個(gè)
跟 MULTIPLE DATABASES WITH BINDS有關(guān)馋嗜,本小節(jié)先忽略.
Part2 - 創(chuàng)建一張表
在SQLAlchemy中齐板,我們可以這樣創(chuàng)建一個(gè)數(shù)據(jù)庫(kù):
from sqlalchemy import create_engine
engine = create_engine('sqlite:///SALTRIVER.db', echo=True)
Base.metadata.create_all(engine)
于是,在電腦硬盤上就生成了一個(gè)SALTRIVER.db的數(shù)據(jù)庫(kù)文件.
那么在Flask-SQLAlchemy中是如何對(duì)其進(jìn)行包裝的呢?
下面我們看看當(dāng)定義完User后甘磨,執(zhí)行db.create_all()會(huì)發(fā)生什么.
以下幾段代碼重點(diǎn)在于我們是怎么獲取engine的听皿,也就是怎么與一個(gè)engine進(jìn)行綁定(bind).
在分析以下幾段代碼的時(shí)候,請(qǐng)先熟讀Flask-SQLAlchemy官方文檔中的第六章.
為熟悉用法,示例代碼可參考test_sqlalchemy.py中的BindsTestCase
將結(jié)合示例代碼對(duì)源碼進(jìn)行說(shuō)明
關(guān)于多引擎綁定宽档,還有一種django-style.詳見(jiàn)Mike Bayer的博客.
在此之前尉姨,我們應(yīng)該知道在init_app中我們?cè)O(shè)置了SQLALCHEMY_DATABASE_URI和
SQLALCHEMY_BINDS兩項(xiàng)參數(shù).
# bind參數(shù)默認(rèn)為'__all__'
def create_all(self, bind='__all__', app=None):
self._execute_for_all_tables(app, bind, 'create_all')
調(diào)用db.create_all,db.drop_all,db.reflect都會(huì)調(diào)用 _execute_for_all_tables().
def _execute_for_all_tables(self, app, bind, operation, skip_tables=False):
# 首先獲取app.
app = self.get_app(app)
if bind == '__all__':
# '__all__'的意思就是把每個(gè)表創(chuàng)建到對(duì)應(yīng)的引擎的中.
# 設(shè)置過(guò)__bind_key__屬性的,創(chuàng)建到bind對(duì)應(yīng)的引擎.
# 沒(méi)設(shè)置過(guò)__bind_key__吗冤,就創(chuàng)建到默認(rèn)的引擎中---'SQLALCHEMY_DATABASE_URI'.
# binds列表中的None就對(duì)應(yīng)沒(méi)bind過(guò)引擎的表.
# 對(duì)于示例代碼就是:Foo -> foo, Bar -> bar, Baz -> default.
binds = [None] + list(app.config.get('SQLALCHEMY_BINDS') or ())
elif isinstance(bind, string_types) or bind is None:
# 如果明確指定, binds就設(shè)置為只含有bind的列表.
# 意思是只在該bind對(duì)應(yīng)的engine中創(chuàng)建Tables.
binds = [bind]
else:
# 比如binds為一個(gè)自行指定的列表.
binds = bind
# 注意skip_tables=False
for bind in binds:
extra = {}
if not skip_tables:
# 獲取一個(gè)bind相關(guān)的tables.
tables = self.get_tables_for_bind(bind)
extra['tables'] = tables
# 獲得self.Model.metadata.create_all方法.
op = getattr(self.Model.metadata, operation)
# 執(zhí)行self.Model.metadata.create_all(bind=engine)
# 原生的create_all可以帶個(gè)可選參數(shù)tables, which is a
# subset of the total tables in the metadata.
# 只創(chuàng)建指定的Tables.
op(bind=self.get_engine(app, bind), **extra)
def get_tables_for_bind(self, bind=None):
"""Returns a list of all tables relevant for a bind."""
result = []
for table in itervalues(self.Model.metadata.tables):
# bind為None,代表對(duì)應(yīng)的默認(rèn)引擎.
if table.info.get('bind_key') == bind:
result.append(table)
return result
下面我們看看是如何獲得get_engine的.
# SQLAlchemy.get_engine()
def get_engine(self, app, bind=None):
# 獲得一把鎖. 思考:為什么需要上鎖又厉?
with self._engine_lock:
# 獲得_SQLAlchemyState實(shí)例.
# state = app.extensions['sqlalchemy'].
state = get_state(app)
# 初次獲取engine的時(shí)候,connector為None.
connector = state.connectors.get(bind)
if connector is None:
# 創(chuàng)建一個(gè)_EngineConnector實(shí)例.
connector = self.make_connector(app, bind)
# 將_EngineConnector加入(緩存)到state.connectors中.
state.connectors[bind] = connector
# 再調(diào)用_EngineConnector的get_engine().
return connector.get_engine()
_EngineConnector主要作用是用來(lái)緩存engine的.
# _EngineConnector.get_engine()
def get_engine(self):
# 思考: 為什么要加瑣?
with self._lock:
# uri = "sqlite:///XYZ.db"
uri = self.get_uri()
echo = self._app.config['SQLALCHEMY_ECHO']
# 最開始, _connected_for為None.
# _connected_for為engine唯一identity(見(jiàn)下方).
if (uri, echo) == self._connected_for:
# 第二次訪問(wèn)該engine時(shí)椎瘟,返回之前緩存在
# _EngineConnector._engine中的engine.
return self._engine
info = make_url(uri)
options = {'convert_unicode': True}
# 設(shè)置SQLALCHEMY相關(guān)的參數(shù), 包括:
# 1, SQLALCHEMY_POOL_SIZE
# 2, SQLALCHEMY_POOL_RECYCLE
# 3, SQLALCHEMY_POOL_TIMEOUT
# 4, SQLALCHEMY_MAX_OVERFLOW
self._sa.apply_pool_defaults(self._app, options)
# What the fuck of this????????????????????????????
self._sa.apply_driver_hacks(self._app, info, options)
if echo:
options['echo'] = True
# 終于, 創(chuàng)建了一個(gè)engine.
self._engine = rv = sqlalchemy.create_engine(info, **options)
# - - - - - - - - - - - - - - - - - - - - - - - - -
# 暫時(shí)忽略.
if _record_queries(self._app):
_EngineDebuggingSignalEvents(self._engine,
self._app.import_name).register()
# - - - - - - - - - - - - - - - - - - - - - - - - -
# 設(shè)置self._connected_for.
# _connected_for相當(dāng)于engine的一個(gè)唯一identity.
# 通過(guò)比較(uri, echo) == self._connected_for是否相等來(lái)
# 判斷某個(gè)engine是非緩存在_EngineConnector中.
self._connected_for = (uri, echo)
# 返回engine.
return rv
返回engine后覆致,最后調(diào)用self.Model.metadata.create_all(bind=engine, **extra).
成功創(chuàng)建數(shù)據(jù)庫(kù)文件.
一個(gè)助于理解的示意圖為:
[PICTURE HERE]
感覺(jué)有循環(huán)引用問(wèn)題?
Part3 查詢一張表
Part3.1 scoped_session
以SQLAlchemy的ORM方式肺蔚,我們通常是這樣開始查詢的:
from sqlalchemy.orm import Session
session = Session(engine)
query = session.query(User)
在Flask-SQLAlchemy中煌妈,我們是如何對(duì)其進(jìn)行包裝改造的呢?
在SQLAlchemy中的init函數(shù)中設(shè)置了scopefunc.
這個(gè)scopefunc與Thread Local Data有關(guān)宣羊,用來(lái)識(shí)別Thread Local Data.
...
if session_options is None:
session_options = {}
session_options.setdefault('scopefunc', connection_stack.__ident_func__)
...
self.session = self.create_scoped_session(session_options)
...
def create_scoped_session(self, options=None):
if options is None:
options = {}
scopefunc = options.pop('scopefunc', None)
return orm.scoped_session(partial(self.create_session, options),
scopedfunc=scopefunc)
Very Important!!!
繼續(xù)之前請(qǐng)仔細(xì)閱讀SQLAlchemy Documentation Release 1.1.0b1中
2.5.6 Contextual/Thread-local Sessions小節(jié)(p233-p238),
一定要仔細(xì)閱讀.在此我就不翻譯了.
下面看看self.create_session: SignallingSession繼承Session璧诵,對(duì)get_bind方法進(jìn)行了重寫.
SignallingSession是Flask-SQLAlchemy默認(rèn)的session.如果想使用一個(gè)不一樣的session.
可對(duì)self.create_session進(jìn)行重寫.
def create_session(self, options):
return SignallingSession(self, **options)
# SignallingSession.__init__().
def __init__(self, db, autocommit=False, autoflush=True, app=None, **options):
self.app = app = db.get_app()
# 暫時(shí)忽略.見(jiàn)event小節(jié).
track_modifications = app.config['SQLALCHEMY_TRACK_MODIFICATIONS']
# 獲得bind參數(shù).
# 單獨(dú)的一個(gè)session需要與一個(gè)engine或者connection綁定,
# 所以SQL操作通過(guò)這個(gè)Connectable完成.
# 如果沒(méi)有明確指定bind參數(shù),bind設(shè)置為db.engine.
# db.engine為默認(rèn)的engine(SQLALCHEMY_DATABASE_URI).
bind = options.pop('bind', None) or db.engine
# 獲得binds參數(shù), 注意bind與binds的區(qū)別.
# db.get_binds() Returns a dictionary with a table->engine mapping.
# binds中的映射還可以是: somemapper --> engine. SomeMappedClass --> engine.
# 對(duì)于示例代碼而言,類似于:
# { Table_baz: Engine(sqlite://),
# Table_bar: Engine(sqlite:///XXX),
# Table_foo: Engine(sqlite:///YYY)
# }
binds = options.pop('binds', None) or db.get_binds(app)
# 暫時(shí)忽略.見(jiàn)event小節(jié).
if track_modifications is None or track_modifications:
_SessionSignalEvents.register(self)
# 調(diào)用原生session的__init__<官方文檔p246>.
SessionBase.__init__(
self, autocommit=autocommit, autoflush=autoflush,
bind=bind, binds=binds, **options
)
說(shuō)說(shuō)SessionBase.init中的參數(shù),注意比較bind和binds的區(qū)別.
bind: An optional Engine or Connectable to which this Session should bound.
When specified, all SQL operations performed by this session will execute
via this Connectable.
binds: binds與multiply-bound有關(guān).假如像這樣傳入binds:
Session = sessionmaker(binds={
SomeMappedClass: create_engine('postgresql://engine1'),
somemapper: create_engine('postgresql://engine2'),
# Flask-SQLAlchemy主要使用這種類型的映射.
some_table: create_engine('postgresql://engine3'),
})
那么,session最后是如何決定是跟哪個(gè)engine進(jìn)行鏈接的呢仇冯?
下面來(lái)看看重寫的get_bind()方法,因?yàn)間et_bind()會(huì)決定到底用binds中的哪一個(gè)bind參數(shù):
|
|
V
```
def get_bind(self, mapper=None, clause=None):
# 關(guān)于mapper, clause會(huì)傳入什么.見(jiàn)'EXTRA MATERIAL'
if mapper is not None:
# 比如session.query(User).all()中, 是怎么獲取bind的呢之宿?
# 知道User class, 便知道了User class的mapper, User.__mapper__,
# 知道了mapper, 便知道了mapper.mapped_table.
# 在Runtime時(shí), 傳入mapper的參數(shù)就是User.__mapper__.
# 在_BoundDeclarativeMeta.__init__函數(shù)中設(shè)置了
# mapped_table的info屬性.
info = getattr(mapper.mapped_table, 'info', {})
# 獲取bind_key參數(shù).
bind_key = info.get('bind_key')
if bind_key is not None:
state = get_state(self.app)
return state.db.get_engine(self.app, bind=bind_key)
# 如果bind_key為None,也就是表沒(méi)有與任何engine綁定,調(diào)用原生的get_bind().
# 原生是怎么決定engine的呢?這需要看sqlalchemy的源碼了.但是這里我們可以
# 猜想一下大概會(huì)是這樣決定engine的: Baz->mapper->mapped_table
# ->metadata->binded_engine. 因?yàn)樵谧畛鮿?chuàng)建Baz這張表的時(shí)候苛坚,會(huì)與Baz
# 這張表所在的metadata在調(diào)用metadata.create_all(bind=engine)中的engine
# 進(jìn)行綁定.到底是不是這樣呢?有興趣可以查看sqlalchemy中的源碼.
return SessionBase.get_bind(self, mapper, clause)
```
此處應(yīng)該有一個(gè)示意圖.
==========================EXTRA MATERIAL=============================
在仔細(xì)剖析重寫的get_bind()函數(shù)之前比被,先來(lái)看看文檔是怎么說(shuō)原生的get_bind()函數(shù)的:
```
get_bind(mapper=None, clause=None)
----------------------------------
For a multiply-bound or unbound Session, the mapper or clause arguments are
used to determine the appropriate bind to return.
Note that the "mapper" argument is usually present when Session.get_bind() is
called via an ORM operation such as a Session.query(), each individual
INSERT/UPDATE/DELETE operation within a Session.flush(), call, etc.
The order of resolution is:
...
Parameters:
* mapper - Optional mapper() mapped class or instance of Mapper. The bind
can be derived from a Mapper first by consulting the "binds" map associated
with this Session, and secondly by consultiong the MetaData associated with
the Table to which the Mapper is mapped for a bind.
* clause - A ClauseElement (i.e. select(), text(), etc.). If the mapper argument
is not present or could not produce a bind, the given expression construct will
be searched for a bound element, typically a Table associated with bound MetaData.
```
下面我們來(lái)看一些示例代碼來(lái)看看get_bind函數(shù)中傳入的參數(shù)到底是什么.
示例代碼選自Mike Bayer的博客, 完整代碼見(jiàn)博客.
調(diào)用add_all或者add的時(shí)候,以s.add(Model1(data='river'))為例, 此時(shí)傳入的參數(shù)為:
Mapper為將Model1映射為model1的Mapper, Clause為None.
with s.begin(): # 11. Writes go to "leader".... s.add_all([ Model1(data='m1_a'), Model2(data='m2_a'), Model1(data='m1_b'), Model2(data='m2_b'), Model3(data='m3_a'), Model3(data='m3_b'), ])
Mapper為將Model1映射為model1的Mapper,Clause為:
<class 'sqlalchemy.sql.expression.Select'>
s.query(Model1).all()
Mapper和Clause都為None.
```
# - - - - - - Added by myself - - - - - - -
from sqlalchemy import select
model1 = Model1.__mapper__.tables[0]
sel = select([model1.c.data])
d = s.connection().execute(sel).fetchall()
# - - - - - - - - - - - - - - - - - - - - -
```
===================================================================
- - - - - - - - - - - - - - - - - - - - - - - -
db.session.add(user1)
db.session.commit()
當(dāng)一個(gè)請(qǐng)求結(jié)束后泼舱,如何處理session呢等缀?
...
# 0.9 and later
if hasattr(app, 'teardown_appcontext'):
teardown = app.teardown_appcontext
...
@teardown
def shutdown_session(response_or_exc):
if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:
if response_or_exc is None:
self.session.commit()
self.session.remove()
return response_or_exc
在SQLAlchemy.init_app()中,我們?yōu)閍pp設(shè)置teardown_appcontext回調(diào)函數(shù).
在Flask中娇昙,每當(dāng)一個(gè)request結(jié)束后尺迂,都會(huì)執(zhí)行注冊(cè)過(guò)@teardown_appcontext的函數(shù),
可以看到在shutdown_session函數(shù)中涯贞,會(huì)調(diào)用self.session.remove()辜纲,將session管理的資源缩麸,
比如connection,返回到connection pool.
Part3.2 - Custom Query Object.
在Flask-SQLAlchemy中,我們還可以這樣查詢:
User.query.first()
或者通沉泻穑可以這樣:
db.session.query(User).first()
第二種很熟悉了, 我們看看第一種是怎么實(shí)現(xiàn)的.
class Model(object):
query_class = BaseQuery
query = None
|
V
def make_declarative_base(self, metadata=None):
# 所有繼承db.Model(也就是base)的類(如User)的基類都將被設(shè)置為cls參數(shù),也就是Model.
# 那么, User也就有了query_class和query屬性.
base = declarative_base(cls=Model, # <---
name='Model',
metadata=metadata,
metaclass=_BoundDeclarativeMeta)
base.query = _QueryProperty(self)
return base
|
V
class _QueryProperty(object):
def __init__(self, sa):
self.sa = sa
def __get__(self, obj, type):
try:
mapper = orm.class_mapper(type)
if mapper:
return type.query_class(mapper, session=self.sa.session())
except UnmappedClassError:
return None
_QueryProperty是個(gè)Non-Data描述符.當(dāng)我們調(diào)用User.query.first()時(shí)或衡,
會(huì)觸發(fā)db.Model.query描述符的get方法執(zhí)行较沪,傳入的參數(shù)obj=None, type=<class User>.
然后獲取User.mapper, orm.class_mapper(type)等同于type.mapper.
最后返回type.query_class(), 也就是type.BaseQuery(),即User.BaseQuery().
BaseQuery繼承自orm.Query.
# Query Object API
class sqlalchemy.orm.query.Query(entities, session=None)
Query的init函數(shù)接受2個(gè)參數(shù)entities和session.實(shí)際上:
# 用法1
session.query(User).all()
# 用法2
Query(User, session).all()
用法1和用法2是一樣的.
于是,type.query_class(mapper, session=self.sa.session())會(huì)返回一個(gè)與
某個(gè)mapper和session一起綁定了的Query Object.
實(shí)際上, User.query.all() 轉(zhuǎn)化為了 session.query(User).all().
Part 4 Miscellanies
1逻翁,BaseQuery中的paginate方法.
paginate主要用來(lái)實(shí)現(xiàn)分頁(yè)功能.一個(gè)簡(jiǎn)單的示例可參考flask web development的11章.
我們一起看看是如何實(shí)現(xiàn)分頁(yè)功能的.
def paginate(self, page=None, per_page=None, error_out=True):
...
items = self.limit(per_page).offset((page - 1) * per_page).all()
...
else:
total = self.order_by(None).count()
return Pagination(self, page, per_page, total, items)
paginate的參數(shù)page代表第幾頁(yè). 這個(gè)參數(shù)是用瀏覽器中的url中的參數(shù)傳遞的.per_page代表沒(méi)頁(yè)有多少個(gè)items. paginate主要干了倆件事薄嫡,第一,獲取該頁(yè)的items.
items = self.limit(per_page).offset((page - 1) * per_page).all()
第二颗胡,獲取total參數(shù).(萬(wàn)一total參數(shù)為百萬(wàn)級(jí)別呢毫深?比如google的搜索結(jié)果?)
然后返回一個(gè)Pagination對(duì)象.我們結(jié)合示例中的模版來(lái)看看Pagination對(duì)象:
{% macro pagination_widget(pagination, endpoint) %}
<ul class="pagination">
# pagination.has_prev().
<li{% if not pagination.has_prev %} class="disabled"{% endif %}>
# 如果pagination.has_prev毒姨,該按鈕會(huì)指向前一頁(yè)的url.
<a href="{% if pagination.has_prev %}{{ url_for(endpoint, page=pagination.prev_num, **kwargs) }}{% else %}#{% endif %}">
?
</a>
</li>
# pagination.iter_pages().
{% for p in pagination.iter_pages() %}
{% if p %}
{% if p == pagination.page %}
<li class="active">
<a href="{{ url_for(endpoint, page = p, **kwargs) }}">{{ p }}</a>
</li>
{% else %}
<li>
<a href="{{ url_for(endpoint, page = p, **kwargs) }}">{{ p }}</a>
</li>
{% endif %}
{% else %}
<li class="disabled"><a href="#">…</a></li>
{% endif %}
{% endfor %}
# pagination.has_next().
<li{% if not pagination.has_next %} class="disabled"{% endif %}>
<a href="{% if pagination.has_next %}{{ url_for(endpoint, page=pagination.next_num, **kwargs) }}{% else %}#{% endif %}">
?
</a>
</li>
</ul>
{% endmacro %}
一圖勝千言.
References and Useful Resources:
- Python Official Documentation: Descriptor HowTo Guide
- Armin Ronacher's slides: Why SQLAlchemy is Awesome
- SQLAlchemy Official Documentation
- [Flask-SQLAlchemy Official Documentation]
- PyCon Session in Depth
- Mike Bayer的博客