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.