一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

使用 SQLAlchemy

 Dawnxu 2009-05-15

Noah Gift, 軟件工程師, Giftcs

2008 年 10 月 23 日

SQLAlchemy 是下一代的 Python Object Relational 映射器。通過本文您將了解如何使用新的 0.5 API、與第三方組件協(xié)作,并構(gòu)建一個(gè)基本的 Web 應(yīng)用程序。

簡介

對(duì) 象關(guān)系映射器(Object Relational Mappers,ORM)在過去數(shù)年吸引了不少人的目光。主要原因是 ORM 經(jīng)常會(huì)在 Web 應(yīng)用程序框架中被提起,因?yàn)樗强焖匍_發(fā)(Rapid Development)棧中的關(guān)鍵組件。Django 和 Ruby on Rails 等 Web 框架采用了設(shè)計(jì)一個(gè)獨(dú)立棧的方法,將自主開發(fā)的 ORM 緊密集成到該框架中。而其他框架,如 Pylons、Turbogears 和 Grok,則采用更加基于組件的架構(gòu)結(jié)合可交換的第三方組件。兩種方法都有各自的優(yōu)勢:緊密集成允許非常連貫的體驗(yàn)(如果問題映射到框架),而基于組件的 架構(gòu)則允許最大的設(shè)計(jì)靈活性。但是,本文的主題并不是 Web 框架;而是 SQLAlchemy。

SQLAlchemy 在構(gòu)建在 WSGI 規(guī)范上的下一代 Python Web 框架中得到了廣泛應(yīng)用,它是由 Mike Bayer 和他的核心開發(fā)人員團(tuán)隊(duì)開發(fā)的一個(gè)單獨(dú)的項(xiàng)目。使用 ORM 等獨(dú)立 SQLAlchemy 的一個(gè)優(yōu)勢就是它允許開發(fā)人員首先考慮數(shù)據(jù)模型,并能決定稍后可視化數(shù)據(jù)的方式(采用命令行工具、Web 框架還是 GUI 框架)。這與先決定使用 Web 框架或 GUI 框架,然后再?zèng)Q定如何在框架允許的范圍內(nèi)使用數(shù)據(jù)模型的開發(fā)方法極為不同。

什么是 WSGI?

WSGI 是下一代 Python Web 框架、應(yīng)用程序和服務(wù)器應(yīng)該遵循的規(guī)范。WSGI 中 一個(gè)有趣的方面是創(chuàng)建 Python 中間件,并在使用 Python 或任何語言創(chuàng)建的 Web 應(yīng)用程序中使用。請(qǐng)參閱 參考資料,獲取關(guān)于 WSGI 和 WSGI 社區(qū)(Pypefitters)的大量鏈接。

SQLAlchemy 的一個(gè)目標(biāo)是提供能兼容眾多數(shù)據(jù)庫(如 SQLite、MySQL、Postgres、Oracle、MS-SQL、SQLServer 和 Firebird)的企業(yè)級(jí)持久性模型。SQLAlchemy 正處于積極開發(fā)階段,當(dāng)前最新的 API 將圍繞版本 0.5 設(shè)計(jì)。請(qǐng)參閱參考資料部分,獲取官方 API 文檔、教程和 SQLAlchemy 書籍的鏈接。

SQLAlchemy 取得成功的一個(gè)證明就是圍繞它已建立了豐富的社區(qū)。針對(duì) SQLAlchemy 的擴(kuò)展和插件包括:declarative、Migrate、Elixir、SQLSoup、django-sqlalchemy、 DBSprockets、FormAlchemy 和 z3c.sqlalchemy。在本文中,我們將學(xué)習(xí)一篇關(guān)于新 0.5 API 的教程,探究一些第三方庫,以及如何在 Pylons 中使用它們。

誰是 Mike Bayer?

Michael Bayer 是居住在紐約的一名軟件承包商,他擁有十余年處理各類關(guān)系數(shù)據(jù)庫的經(jīng)驗(yàn)。他曾使用 C、Java? 和 Perl 編寫了許多自主研發(fā)的數(shù)據(jù)庫抽象層,并在 Major League Baseball 與大量多服務(wù)器 Oracle 系統(tǒng)打了多年交道,借助這些經(jīng)驗(yàn),他成功編寫了 “終極工具包” SQLAlchemy,用于生成 SQL 和處理數(shù)據(jù)庫。其目標(biāo)是貢獻(xiàn)一個(gè)世界級(jí)、獨(dú)樹一幟的面向 Python 的工具包,以幫助 Python 成為一個(gè)廣泛普及的編程平臺(tái)。

安裝

本文假定您使用 Python 2.5 或更高版本,并且安裝了子版本。Python 2.5 包括 SQLite 數(shù)據(jù)庫,因此也是測試 SQLALchemy 內(nèi)存的好工具。如果您已經(jīng)安裝了 Python 2.5,則只需通過設(shè)置工具安裝 sqlalchemy 0.5 beta 。要獲取設(shè)置工具腳本,請(qǐng)?jiān)谀慕K端中下載并運(yùn)行以下 4 條命令:

  wget http://peak./dist/ez_setup.py
                    python ez_setup.py
                    sudo easy_install http://svn./sqlalchemy/trunk
                    sudo easy_install ipython
                    

前 三行代碼檢查最新版本的 sqlalchemy,并將它作為包添加到您本地系統(tǒng)的 Python 安裝中。最后一個(gè)代碼片段將安裝 IPython,它是一個(gè)實(shí)用的聲明式 Python 解釋器,我將在本文中使用它。首先,我需要測試已安裝的 SQLAlchemy 版本。您可以測試自己的版本是否為 0.5.x,方法是在 IPython 或普通 Python 解釋器中發(fā)起以下命令。

 In [1]: import sqlalchemy
                    In [2]: sqlalchemy.__version__
                    Out[2]: '0.5.0beta1'
                    

SQLAlchemy 0.5 快速入門指南

新的 0.5 發(fā)行版在 SQLAlchemy 中引入了一些顯著的變更。此外列出了這些變更的概要信息:

  • 聲明式擴(kuò)展是多數(shù)情況下建議的開始方式。
  • session.query() 可以接受任意組合的 class/column 表達(dá)式。
  • session.query() 或多或少也是 select() 的支持 ORM 的替代方法。
  • 查詢提供了一些試驗(yàn)性的 update()/delete() 方法,用于實(shí)現(xiàn)基于標(biāo)準(zhǔn)的更新/刪除。
  • 會(huì)話將在 rollback() 和 commit() 方法后自動(dòng)過期;因此使用默認(rèn)的 sessionmaker() 意味著您通常不必調(diào)用 clear() 或 close();對(duì)象將自動(dòng)與當(dāng)前的事務(wù)同步。
  • 使用 session.add()、session.add_all()(save/update/save_or_update 已刪除)在會(huì)話中添加內(nèi)容。

雖然 declarative 擴(kuò)展從 0.4 開始便一直出現(xiàn)在 SQLAlchemy 中,但它也經(jīng)過了一些小修改,這使它在大多數(shù) SQLAlchemy 項(xiàng)目中都成為了一種強(qiáng)有力的便捷方式。新的 declarative 語法允許在一步中創(chuàng)建表、類和數(shù)據(jù)庫映射。下面我們來看看這種新語法的工作原理,以我編寫的一個(gè)用于跟蹤文件系統(tǒng)變化的工具為例。


清單 1. 新 SQLAlchemy 聲明樣式
                    #/usr/bin/env python2.5
                    #Noah Gift
                    from sqlalchemy.ext.declarative import declarative_base
                    from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
                    Base = declarative_base()
                    class Filesystem(Base):
                    __tablename__ = 'filesystem'
                    path = Column(String, primary_key=True)
                    name = Column(String)
                    def __init__(self, path,name):
                    self.path = path
                    self.name = name
                    def __repr__(self):
                    return "<Metadata('%s','%s')>" % (self.path,self.name)
                    

通 過這種新的聲明樣式,SQLAlchemy 能夠在一步中創(chuàng)建一個(gè)數(shù)據(jù)庫表、創(chuàng)建一個(gè)類以及類與表之間的映射。如果您剛開始接觸 SQLAlchemy,或許應(yīng)該學(xué)習(xí)這種建立 ORM 的方法。此外,了解另一種更加顯式地控制各步驟的方式也是有益的(如果您的項(xiàng)目要求這種級(jí)別的詳細(xì)程度)。

在閱讀這段代碼時(shí),需要指出一些可能會(huì)讓初次接觸 SQLAlchemy 或聲明性擴(kuò)展的用戶犯難的地方。首先,

Base = declarative_base()

行創(chuàng)建了一個(gè)類,稍后的 Filesystem 類便繼承自該類。如果您保存并在 declarative_style 中運(yùn)行該代碼,然后將它導(dǎo)入到 IPython 中,則會(huì)看到以下輸出:
In [2]: declarative_style.Filesystem?
                    Type:		DeclarativeMeta
                    Base Class:	<class 'sqlalchemy.ext.declarative.DeclarativeMeta'>
                    String Form:	<class 'declarative_style.Filesystem'>
                    

這個(gè) DeclarativeMeta 類型的魔力就是允許所有操作發(fā)生在一個(gè)簡單的類定義中。

另一個(gè)需要指出的地方是本示例并未實(shí)際執(zhí)行任何操作。在運(yùn)行創(chuàng)建表的代碼之前,將不會(huì)創(chuàng)建實(shí)際的表,并且,您還需要定義 SQLAlchemy 將使用的數(shù)據(jù)庫引擎。這兩行代碼如下所示:

       engine = create_engine('sqlite:///meta.db', echo=True)
                    Base.metadata.create_all(engine)
                    

SQlite 是試驗(yàn) SQLAlchemy 的理想選擇,并且您還可以選擇使用內(nèi)存數(shù)據(jù)庫,在這種情況下,您的代碼行應(yīng)如下所示:

        engine = create_engine('sqlite:///:memory:', echo=True)
                    

或者,只創(chuàng)建一個(gè)簡單的文件,如第一個(gè)例子所示。如果您選擇創(chuàng)建一個(gè)基于 SQLite 文件的數(shù)據(jù)庫,則可以通過拋棄數(shù)據(jù)庫中的所有表從零開始,而不需要?jiǎng)h除文件。為此,您可以發(fā)起以下代碼行:

                    Base.metadata.drop_all(engine)
                    

此 時(shí),我們已經(jīng)了解了創(chuàng)建 SQLAlchemy 項(xiàng)目和通過 SQLAlchemy API 控制數(shù)據(jù)庫所需的知識(shí)。在開始實(shí)際應(yīng)用之前,惟一需要掌握一點(diǎn)是會(huì)話的概念。SQLAlchemy “官方” 文檔將會(huì)話描述為數(shù)據(jù)庫的句柄。在實(shí)際應(yīng)用中,它允許不同的基于事務(wù)的連接發(fā)生在 SQLAlchemy 一直在等待的連接池中。在會(huì)話內(nèi)部,這通常是添加數(shù)據(jù)到數(shù)據(jù)庫中、執(zhí)行查詢或刪除數(shù)據(jù)。

要?jiǎng)?chuàng)建會(huì)話,請(qǐng)執(zhí)行下面這些后續(xù)步驟:

        #establish Session type, only need to be done once for all sessions
                    Session = sessionmaker(bind=engine)
                    #create record object
                    create_record = Filesystem("/tmp/foo.txt", "foo.txt")
                    #make a unique session
                    session = Session()
                    #do stuff in session.  We are adding a record here
                    session.add(create_record)
                    #commit the transaction
                    session.commit()
                    

這 些就是使 SQLAlchemy 正常運(yùn)行所需的所有工作。雖然 SQLAlchemy 提供了一個(gè)非常復(fù)雜的 API 來處理許多復(fù)雜的事情,但它實(shí)際上非常容易使用。在本節(jié)結(jié)束時(shí),我還想指出,上例使用 echo=True 創(chuàng)建引擎。這是查看由 SQLAlchemy 創(chuàng)建的 SQL 的便捷方法。對(duì)于 SQLAlchemy 初學(xué)者,強(qiáng)烈建議使用該方法,因?yàn)樗鼤?huì)讓您覺得 SQLAlchemy 不再那么神秘?,F(xiàn)在,運(yùn)行自己創(chuàng)建的一些代碼,并查看 SQL 創(chuàng)建表的過程。


清單 2. SQLAlchemy SQL 表創(chuàng)建輸出
2008-06-22 05:33:46,403 INFO
                    sqlalchemy.engine.base.Engine.0x..ec PRAGMA
                    table_info("filesystem")
                    2008-06-22 05:33:46,404 INFO sqlalchemy.engine.base.Engine.0x..ec {}
                    2008-06-22 05:33:46,405 INFO sqlalchemy.engine.base.Engine.0x..ec
                    CREATE TABLE filesystem (
                    path VARCHAR NOT NULL,
                    name VARCHAR,
                    PRIMARY KEY (path)
                    )
                    

Pylesystem:類似于 Spotlight 或 Beagle 的實(shí)時(shí)文件系統(tǒng)元數(shù)據(jù)索引程序

抽 象地討論如何使用某個(gè)工具會(huì)讓許多人不好理解,因此,我將使用 SQLAlchemy 演示如何創(chuàng)建一個(gè)元數(shù)據(jù)工具。此工具的目標(biāo)是監(jiān)控文件系統(tǒng)、創(chuàng)建和刪除事件,以及在一個(gè) SQLAlchemy 數(shù)據(jù)庫中保存這些變更的記錄。如果您曾經(jīng)在 OS X 上使用過 Spotlight,或在 Linux? 上使用過 Beagle,那就應(yīng)該使用過一款實(shí)時(shí)的文件系統(tǒng)索引工具。要繼續(xù)本文,您需要運(yùn)行 Linux 內(nèi)核 2.6.13 或更高版本。

下一個(gè)示例比較大,大約有 100 行代碼。查看整個(gè)示例并運(yùn)行它,然后,我將介紹代碼各部分的作用。要運(yùn)行此腳本,您必須在終端中執(zhí)行以下步驟:

  1. wget http://peak./dist/ez_setup.py
  2. sudo python ez_setup.py
  3. sudo easy_install
    "http://git./?p=pyinotify.git;a=snapshot;h=HEAD;sf=tgz"
  4. sudo easy_install http://svn./sqlalchemy/trunk

清單 3. 文件系統(tǒng)事件監(jiān)控?cái)?shù)據(jù)庫
#/usr/bin/env python2.5
                    #Noah Gift 06/21/08
                    #tweaks by Mike Bayer 06/22/08
                    import os
                    from sqlalchemy.ext.declarative import declarative_base
                    from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
                    from sqlalchemy import create_engine
                    from sqlalchemy.orm import sessionmaker
                    from sqlalchemy.orm import scoped_session
                    from pyinotify import *
                    path = "/tmp"
                    #SQLAlchemy
                    engine = create_engine('sqlite:///meta.db', echo=True)
                    Base = declarative_base()
                    Session = scoped_session(sessionmaker(bind=engine))
                    class Filesystem(Base):
                    __tablename__ = 'filesystem'
                    path = Column(String, primary_key=True)
                    name = Column(String)
                    def __init__(self, path,name):
                    self.path = path
                    self.name = name
                    def __repr__(self):
                    return "<Metadata('%s','%s')>" % (self.path,self.name)
                    def transactional(fn):
                    """add transactional semantics to a method."""
                    def transact(self, *args):
                    session = Session()
                    try:
                    fn(self, session, *args)
                    session.commit()
                    except:
                    session.rollback()
                    raise
                    transact.__name__ = fn.__name__
                    return transact
                    class ProcessDir(ProcessEvent):
                    """Performs Actions based on mask values"""
                    @transactional
                    def process_IN_CREATE(self, session, event):
                    print "Creating File and File Record:", event.pathname
                    create_record = Filesystem(event.pathname, event.path)
                    session.add(create_record)
                    @transactional
                    def process_IN_DELETE(self, session, event):
                    print "Removing:", event.pathname
                    delete_record = session.query(Filesystem).                    filter_by(path=event.pathname).one()
                    session.delete(delete_record)
                    def init_repository():
                    #Drop the table, then create again with each run
                    Base.metadata.drop_all(engine)
                    Base.metadata.create_all(engine)
                    session = Session()
                    #Initial Directory Walking Addition Brute Force
                    for dirpath, dirnames, filenames in os.walk(path):
                    for file in filenames:
                    fullpath = os.path.join(dirpath, file)
                    record = Filesystem(fullpath, file)
                    session.add(record)
                    session.flush()
                    for record in session.query(Filesystem):
                    print "Database Record Number: Path: %s , File: %s "                     % (record.path, record.name)
                    session.commit()
                    if __name__ ==  "__main__":
                    init_repository()
                    #Pyionotify
                    wm = WatchManager()
                    mask = IN_DELETE | IN_CREATE
                    notifier = ThreadedNotifier(wm, ProcessDir())
                    notifier.start()
                    wdd = wm.add_watch(path, mask, rec=True)
                    

要查看此腳本的實(shí)際運(yùn)行結(jié)果,您需要打開兩個(gè)終端窗口。在第一個(gè)窗口中,運(yùn)行 pylesystem.py 腳本。您將看到一系列輸出內(nèi)容,如下所示(請(qǐng)注意,以下版本經(jīng)過適當(dāng)縮減):

2008-06-22 07:18:08,707 INFO
                    sqlalchemy.engine.base.Engine.0x..ec ['/tmp/ba.txt', 'ba.txt']
                    2008-06-22 07:18:08,710 INFO
                    sqlalchemy.engine.base.Engine.0x..ec COMMIT
                    2008-06-22 07:18:08,715 INFO
                    sqlalchemy.engine.base.Engine.0x..ec BEGIN
                    2008-06-22 07:18:08,716 INFO
                    sqlalchemy.engine.base.Engine.0x..ec SELECT filesystem.path
                    AS filesystem_path, filesystem.name AS filesystem_name
                    FROM filesystem
                    2008-06-22 07:18:08,716 INFO sqlalchemy.engine.base.Engine.0x..ec []
                    Database Record Number: Path: /tmp/ba.txt , File: ba.txt
                    

第一個(gè)腳本運(yùn)行一個(gè)多線程文件系統(tǒng)事件監(jiān)控引擎,它將 /tmp 的所有創(chuàng)建和刪除變更寫入到 sqlalchemy 數(shù)據(jù)庫中。注意:由于它是多線程的,當(dāng)您 完成此教程時(shí),需要鍵入 Control + \ 來停止線程應(yīng)用程序。

成功運(yùn)行之后,您可以在第二個(gè)終端窗口中創(chuàng)建事件,新創(chuàng)建或刪除的文件將實(shí)時(shí)添加到數(shù)據(jù)庫中或從數(shù)據(jù)庫中刪除。如果您只創(chuàng)建了 /tmp 目錄中的某個(gè)文件,比如說 touch foobar.txt,則會(huì)在第一個(gè)窗口中看到以下輸出:

Creating File and File Record: /tmp/foobar.txt
                    2008-06-22 08:02:19,468 INFO
                    sqlalchemy.engine.base.Engine.0x..4c BEGIN
                    2008-06-22 08:02:19,471 INFO
                    sqlalchemy.engine.base.Engine.0x..4c INSERT INTO filesystem (path, name) VALUES (?, ?)
                    2008-06-22 08:02:19,472 INFO
                    sqlalchemy.engine.base.Engine.0x..4c ['/tmp/foobar.txt', '/tmp']
                    2008-06-22 08:02:19,473 INFO
                    sqlalchemy.engine.base.Engine.0x..4c COMMIT
                    

記得您之前啟用了 SQL echo 嗎?鑒于此,當(dāng)代碼將此新條目添加到文件系統(tǒng)中時(shí),您可以看到 SQL 語句。如果您現(xiàn)在刪除該文件,您也可以看到刪除的過程。下面是您鍵入 rm 語句 rm foobar.txt 時(shí)的輸出:

Removing: /tmp/foobar.txt
                    2008-06-22 08:06:01,727 INFO
                    sqlalchemy.engine.base.Engine.0x..4c BEGIN
                    2008-06-22 08:06:01,733 INFO
                    sqlalchemy.engine.base.Engine.0x..4c SELECT filesystem.path
                    AS filesystem_path, filesystem.name AS filesystem_name
                    FROM filesystem
                    WHERE filesystem.path = ?
                    LIMIT 2 OFFSET 0
                    2008-06-22 08:06:01,733 INFO
                    sqlalchemy.engine.base.Engine.0x..4c ['/tmp/foobar.txt']
                    2008-06-22 08:06:01,736 INFO
                    sqlalchemy.engine.base.Engine.0x..4c DELETE FROM filesystem WHERE filesystem.path = ?
                    2008-06-22 08:06:01,736 INFO
                    sqlalchemy.engine.base.Engine.0x..4c [u'/tmp/foobar.txt']
                    2008-06-22 08:06:01,737 INFO
                    sqlalchemy.engine.base.Engine.0x..4c COMMIT
                    

在 Filesystem 類中,您添加了一個(gè) transactional 方法,您將使用一個(gè)修飾類來處理將文件系統(tǒng)事件提交給數(shù)據(jù)庫的語義。Pyinotify 中的實(shí)際 Filesystem I/O 監(jiān)控由 ProcessDir 類完成,該類繼承自 ProcessEvents 并覆蓋了其中的方法。如果您注意了 process_IN_CREATE 和 process_IN_DELETE 方法,會(huì)發(fā)現(xiàn)它們都附加了一個(gè) transactional 修飾類。隨后,它們將接受創(chuàng)建或刪除事件并對(duì)數(shù)據(jù)庫執(zhí)行修改。

還有一個(gè)名稱為 initial_repository 的方法,每次運(yùn)行腳本時(shí)它都會(huì)填充數(shù)據(jù)庫,實(shí)現(xiàn)方法是銷毀數(shù)據(jù)庫中的表并重新創(chuàng)建。腳本的最底部將通知 Pyinotify 代碼以不確定的方式運(yùn)行,而這最終表示作為守護(hù)進(jìn)程運(yùn)行。

結(jié)束語

本文介紹了 SQLAlchemy 的一些特性,并演示了它和 API 的使用是多么簡單。借助 SQLAlchemy 和開源庫 Pyinotify,您還使用不到 100 行 Python 代碼構(gòu)建了一個(gè) 功能異常強(qiáng)大的工具。這是簡單但功能強(qiáng)大的 ORM 的特性之一。它消除了復(fù)雜的關(guān)系數(shù)據(jù)庫處理操作,現(xiàn)在它為用戶添加了快樂而不是負(fù)擔(dān)。隨后,這些省下來的精力可以用于解決感興趣的問題,因?yàn)?SQLAlchemy 將是最簡單的環(huán)節(jié)。

如果您有興趣了解更多關(guān)于 SQLAlchemy 的信息,則應(yīng)該閱讀本文末尾列出的 參考資料。 其中包括一本出色的書籍和大量優(yōu)秀的在線文檔,您可以考慮研究其他一些使用 SQLAlchemy 的項(xiàng)目并擴(kuò)展它們。最近一個(gè)較有興趣的 SQLAlchemy 相關(guān)項(xiàng)目就是 Website reddit.com。它使用純 WSGI 框架 Pylons 構(gòu)建,并整合了 SQLAlchemy 作為其默認(rèn) ORM。我附帶了到 reddit 完整源代碼的鏈接。借助新掌握的 SQLAlchemy 知識(shí),您應(yīng)該能夠快速實(shí)現(xiàn)自己的 reddit,并且應(yīng)該能執(zhí)行一些數(shù)據(jù)庫查詢操作。祝您好運(yùn)!

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    妻子的新妈妈中文字幕| 日韩一区二区三区久久| 国产丝袜极品黑色高跟鞋| 激情中文字幕在线观看| 99久久国产精品免费| 丰满少妇被粗大猛烈进出视频 | 中文字幕一区二区三区大片| 俄罗斯胖女人性生活视频| 国产丝袜极品黑色高跟鞋| 日本不卡一本二本三区| 嫩草国产福利视频一区二区| 精品国产丝袜一区二区| 精品人妻一区二区三区免费| 欧美日本亚欧在线观看| 日韩中文字幕狠狠人妻| 成年女人下边潮喷毛片免费| 国产精品亚洲综合色区韩国| 欧美日韩人妻中文一区二区| 我想看亚洲一级黄色录像| 欧美日本精品视频在线观看| 偷拍美女洗澡免费视频| 日本免费熟女一区二区三区| 色丁香一区二区黑人巨大| 中文字幕91在线观看| 91亚洲精品亚洲国产| 欧美一级特黄特色大色大片| 日韩人妻毛片中文字幕| 日本欧美一区二区三区就| 国产欧美一区二区久久| 欧美日韩亚洲综合国产人| 久久精品亚洲精品国产欧美| 国产精品不卡免费视频| 国产亚洲不卡一区二区| 日韩色婷婷综合在线观看| 国产激情一区二区三区不卡| 国产精品久久熟女吞精| 五月婷婷缴情七月丁香| 久久热在线视频免费观看| 欧美国产亚洲一区二区三区| 久久国产精品热爱视频| 精品午夜福利无人区乱码|