GINO 填補了國內外 asyncio ORM 領域的空白python
隨着 Tornado 和 asyncio 等框架的陸續涌現,Python 異步編程這個話題也在逐漸升溫。在這個燒腦的異步世界裏,有沒有辦法能夠既方便快捷、又簡單明瞭地訪問數據庫呢?GitHub 千星項目 GINO 瞭解一下!linux
1. GINO 是誰
GINO 是一個「輕量級」異步 ORM 框架,它的全稱是 GINO Is Not ORM,借鑑了 GNU is Not Unix 的遞歸定義手法。因此,GINO 必定要全!部!大!寫!若是像這樣「Gino」就變成了人名,你確定要問一句「這是誰」。git
ORM,即關係對象映射(Object-Relational Mapping),是一類開發人員喜聞樂見的效率工具,它們"極大地"提高了寫代碼的幸福指數。GINO 是用來訪問數據庫的,也提供了對象映射的工具,那爲何非說 GINO 不是 ORM 呢?github
由於物極必反,ORM 在帶來生活便利的同時,也是 bug 生長的溫牀 —— 傳統 ORM 每每會選擇犧牲明確性(explicitness)來換取便捷性(convenience),再加上 Python 得天獨厚的靈活性(flexibility),創造出了一種爆炸式的化學反應。一旦代碼初具規模,項目或多或少都會遇到 ORM 反噬的情景:性能莫名其妙的差、出問題找不到緣由、爲了雞毛蒜皮的小事大動干戈。隨便一句 current_user.name 都有可能觸發一大堆意想不到的數據庫調用,這代碼你讓我怎麼調試?數據庫
傳統 ORM 的學習曲線前平後陡,能在快速原型開發中大展身手,但應用到大型項目中卻十分考驗開發人員的平均水平。編程
全部這些問題若是再放進異步編程的環境裏,那就是 O(n2) 的複雜度了 —— 哦不,是 O(2n)。這對於一款優秀的異步 ORM 框架來講是不可接受的,因此 GINO 是 ORM 但不是一個傳統的 ORM,正猶如 GNU 不是一個傳統的 Unix 同樣,形似而神不似。api
因此在 2017 年創做之初,我就給 GINO 定下了兩個業績目標:1) 方便快捷,2) 簡單明瞭。三年後的今天,我索性在 1.0 穩定版發佈的前夕作個年終總結。併發
2. 先說"方便快捷"
"方便快捷"主要說的是開發效率。app
重視開發效率的概念對於寫 Python 的同窗來講可能並不陌生,某些場景下,開發人員的時間確實比機器的時間值錢。因此,傳統 ORM 裏的對象映射不能丟。框架
from gino import Gino db = Gino() class User(db.Model): __tablename__ = "users" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String)
這麼定義表結構甚至讓人有點小興奮。咦,爲何這麼眼熟?
沒有錯,這就是 SQLAlchemy ORM 的定義風格。GINO 並非從頭造輪子,而是在 SQLAlchemy core(SQLAlchemy 中負責構建 SQL 的底層核心)的基礎上開發的。這麼作除了能保持熟悉的味道(以節省學習和遷移成本),更重要的是帶來了整個 SQLAlchemy 的生態環境:開箱即用的數據庫變動管理工具 Alembic、各類 SQLAlchemy 的加強插件、專業領域的 PostGIS/geoalchemy 等,GINO 全都兼容。
是否是十分方便、十分快捷?不止這樣。
GINO 一站式地解決了經常使用 CRUD 快捷方式、上下文管理(aiocontextvars)、數據庫事務封裝和嵌套、鏈接池管理和懶加載等多項便捷功能,無額外依賴關係,即裝即用。
daisy = await User.create(name="daisy") await daisy.update(name="Daisy").apply()
GINO 還提供了各大流行異步 Web 框架的定製版插件,能叫上名字的像 Tornado、aiohttp、Sanic、FastAPI/Starlette、Quart 什麼的都有,從簡單示範到生產環境的各類例子品種齊全,媽媽不再用擔憂我不會集成 Web 框架了。
爲了讓不一樣應用場景下的用戶體驗到最大的善意,GINO 目前支持三種不一樣程度的用法,成功實現了對同期競品 asyncpgsa 的降維打擊:
- 最少侵入型:SQLAlchemy core 原教旨主義者,只有異步執行時纔用到 GINO。
- 終身不婚型:天生厭惡"對象",只願定義"表",空手接 SQL。
- 火力全開型:最大程度的便利,非典型異步 ORM。
最後,雖然是 Python(毫不是黑哈),但 GINO 在執行效率上也沒落下。基於 MagicStack 出品必屬精品的、一秒可讀百萬行的 asyncpg,以及 uvloop(可選)的強力加持,GINO 跑起來也是能夠飛快的,被普遍應用於諸如實時匯率、聊天機器人、在線遊戲等高併發領域,深受俄羅斯和烏克蘭人民的愛戴。
3. 再說"簡單明瞭"
Explicit is better than implicit. Simple is better than complex. -- The Zen of Python, PEP 20
Python 之禪完美表達了 GINO 的立場 —— 明確性(explicitness)對於上了規模的異步工程項目來講尤其重要,所以 GINO 的不少設計都受到了明確性的影響。
好比說,GINO 的 Model
是徹底無狀態的普通 Python 對象(POPO)—— 例如前面的 User
類,它的實例 daisy
就是內存裏面的常規對象,你能夠用 daisy.name
訪問屬性,也能夠用 daisy.name = "DAISY"
來修改屬性,或者用 u = User()
來建立新的實例,這些操做都不會訪問數據庫,絕對綠色環保無毒反作用。
等到須要操做數據庫的時候,你必定會有感知的。好比執行 INSERT
要用 u = await User.create()
,而 UPDATE
則是 await u.update(name="Daisy").apply()
。
其中,
u.update(name="Daisy")
與u.name = "Daisy"
相似,都是隻在內存裏修改對象的屬性,不一樣的是u.update()
還會返回一個包含本次變動的中間結果,對其執行await xxx.apply()
則會將這些變動應用到數據庫裏。
這裏的 await
就是明確性的關鍵,意味着咱們要跳出去執行數據庫操做了。換句話說,沒有 await
就沒有數據庫操做。
另外一方面,對於如何將數據庫查詢結果組裝成內存對象及其屬性,GINO 也有一套精妙的顯式機制 —— 可定製化的加載器 loaders。對於簡單直觀的一對一加載,GINO 天然是伺候到家的,好比用 u = await User.get(1)
能夠直接獲取到 ID 爲 1 的用戶對象。可是對於更復雜的查詢,GINO 不會去無故猜想主人的意圖,而全權交給用戶來明確地定義。加載器的用法也是很簡單的,好比一個用戶可能寫了不少本書:
class Book(db.Model): __tablename__ = "books" id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) author_id = db.ForeignKey("users.id")
而後這樣來加載這種多對一關係,以同時獲取全部的書和他們的做者:
query = Book.outerjoin(User).select() loader = Book.load(author=User) async for book in query.gino.load(loader).iterate(): print(book.title, "written by", book.author.name)
很簡單的一個外鏈接查詢 Book.outerjoin(User)
,配合一個直觀的加載器 Book.load(author=User)
,就實現了:
- 執行 SELECT * FROM books LEFT JOIN users ON ...;
- 將數據庫返回結果的每一行中,屬於 books 的字段加載成一個 Book 實例;
- 而後將該行中剩下的屬於 users 的字段加載成一個 User 實例;
- 最後將 User 實例設置到 Book 實例的 author 屬性上。
既簡單又明瞭有沒有!你甚至能夠手寫任何 SQL,而後定製加載器自動加載成指望的對象關係,精準控制加載行爲,指哪兒打哪兒。GINO 還有不少相似的特性,在這裏就不一一列舉了。
4. 優點與不足
隨着這幾年 GINO 不斷演進成熟,Python 開源社區裏也相繼出現了像 Tortoise ORM、ORM(是的,這個項目就叫 ORM......我真 ORZ。出品方是 Encode,Starlette 就是他們的做品)等優秀的異步 ORM 框架。它們關注的重點與 GINO 稍有不一樣,但都是同行就很少評價了。
GINO 的最大優點仍是在於充分平衡了開發效率和明確性之間的辯證矛盾關係,用 GINO 開發應用程序的時候不用擔憂會被意料以外的行爲所驚嚇到,同時也不須要爲這種明確性付出過大的工程代價,上手後依然能夠快速、快樂地編程。同時,大量的成功案例也證實了 GINO 已經初步具有發佈 1.0 穩定版的各類條件,能夠謹慎地用於生產環境了。
如下是近來統計到的關於 GINO 的應用案例:
- 筆者工做上開發的一個服務:https://github.com/uc-cdis/metadata-service
- 仍是筆者本身寫的一個工具:https://github.com/fantix/aintq
- 一個匯率 API 服務:https://exchangeratesapi.io/
- 各類 Telegram、Discord 的 Bot。
- ArchLinux 用戶包:https://aur.archlinux.org/packages/python-gino/
- https://github.com/bryanforbes/gino-stubs/
- 俄語教程:https://www.youtube.com/watch?v=WW4rOnfhiQY
- 高性能模板項目:https://github.com/leosussan/fastapi-gino-arq-uvicorn
- 還有幾個商用的,但沒徵得贊成就不貼出來了。
另外,GINO 還貼心地提供了中文文檔,從上手教程到原理說明應有盡有(雖然文檔還在努力編寫中!):
GINO 目前的不足之處還有一些,好比沒有照顧到 Python 3 的類型提示,所以還不能徹底發揮 IDE 的潛能(上面那個 gino-stubs 就是有人受不了了本身寫了一個類型註解)。MySQL 目前也是不支持的,但 GINO 從比較早就解耦了不一樣 SQL 方言和驅動的集成,因此這些功能會陸續在 1.1 和 1.2 版本中跟上。
5. 建設社會主義
GINO 是一個開源項目,因此歡迎你們一塊兒來建設!長期活躍的貢獻者還能獲贈價值 4888 元 的 PyCharm 專業版全家桶 License 一枚。 目前急需幫助的有:
- 各個 Web 框架插件的維護工做須要多人認領;
- 更多的例子和文檔,以及中文、俄文的翻譯;
- MySQL 的支持。
以及下面這些一直須要的幫助:
- 用 GINO,找 bug,提建議;
- 修 bug,作功能,提 PR;
- 維護社區,回答問題,參與討論;
- 最後也是最重要的:去 GitHub 上給 GINO 加一顆星星!
6. 關於做者
I'm a software architect, fan of coding for over 20 years, and now focusing on software engineering, high concurrency and development performance. Python is my chief language, and I worked on Linux for 10 years, managing development teams up to 20 people for 8 years. Big fan of open source, created project GINO with 1000+ GitHub stars.
OPEN SOURCE
Python / 2018-2019 I started to contribute to Python programming language with MagicStack fellows, focusing on the asyncio library.
GINO / 2017-2019 GINO is an ORM library for Python asyncio. It is an integration of SQLAlchemy core and asyncpg.
aioh2 / 2016 This is an integration of an HTTP/2 protocol library and Python 3 asyncio.
tulipcore / 2015 I've implemented the most part of the event loop core of Gevent with pure Python 3 asyncio (tulip) code.
zmq.rs / 2014 It was my attempt to implement the ZeroMQ stack in pure Rust.
ArchLinux / 2013 - 2016 I maintained the packages of a dozen of build toolchains and base libraries of ArchLinux for x32-ABI, e.g. GCC, glibc, openssl, curl, util-linux, etc.
Gevent / 2011 - 2013 I contributed the initial port of Gevent to Python 3, which was later merged into Gevent 1.1 by its new maintainer. I've also ported Greenlet to x32-ABI.
Translations / 2008 - 2015 I was involved/started several translation projects, e.g. Ubuntu, libexif, Twisted, Python-beginners, ZeroMQ, etc.