将SQL和Python与Sqlorm结合
Blending SQL and Python with Sqlorm

原始链接: https://hyperflask.dev/blog/2025/11/11/blending-sql-and-python-with-sqlorm/

## SQLORM:一种直接SQL的ORM方法 SQLORM是一个新的Python ORM,其设计灵感源于对现有框架(尤其是SQLAlchemy)的设计选择的反思。它的创建者更喜欢直接的SQL控制,并摒弃了工作单元模式,旨在实现立即查询执行和对象与会话的独立性。 SQLORM的核心原则是优先考虑SQL。查询被定义为标准的Python函数,其中SQL语句嵌入在文档字符串中,利用参数转义。连接和事务通过使用`Engine`类作为上下文管理器进行管理。 虽然强调SQL,SQLORM也提供了一个Active Record `Model`类,用于基本的CRUD操作,并带有Python注解用于类型提示和模式生成。重要的是,模型不绑定到特定数据库,从而能够实现灵活的读/写模式,例如查询副本和更新主数据库。 SQLORM提供了用于构建SQL和获取相关数据的工具,并包含关系和延迟加载等功能。它文档完善,并与Flask集成,为那些希望获得接近DB-API的ORM体验的开发者提供了一个强大的替代方案。

将SQL和Python融合的Sqlorm (hyperflask.dev) 3点 由 emixam 1小时前 | 隐藏 | 过去 | 收藏 | 1条评论 Rajni07 1小时前 [–] 非常喜欢保持SQL明确的同时,仍然获得ORM便利的想法。@sqlfunc语法感觉简洁,无session的方法使跨数据库工作更简单。好奇它如何处理异步或连接池。看起来是原始SQL和大型ORM之间的一个坚实的中介。回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请YC | 联系 搜索:
相关文章

原文

At $dayjob, I use SQLAlchemy as an ORM. It's an amazing project, powerful and flexible. However, I've always felt some of the design choices didn't fit how I like to use an ORM. Notably:

  • I'm not a big fan of the Unit of Work pattern and the fact that you do not control when DML queries are issued. I like queries to be executed as soon as they are called in code.
  • I do not want my objects to be "attached" to a session or a specific database. I want to be able to fetch from one database and insert into another using the same object.
  • I mostly want plain objects to map a table row.
  • I want to write SQL by hand for complex queries. I do not want to use a query builder or DSL, I prefer writing actual SQL.
  • I do not care about abstracting the database. I usually choose a database server when starting a project and optimize for it.
  • Stay as close to DB-API as possible.

With these ideas in mind, SQLORM was born. (The name isn't great — I'm bad at finding names for projects like this.) It's inspired by many ORMs while bringing some unique features.

(As a side note, I know many other Python ORMs exist, but to me SQLAlchemy is the best. I don't like the API or codebase of the others.)

The main feature of SQLORM is that SQL is front and center. You can create SQL queries as standard Python functions, using the docblock to write the templated SQL statement:

from sqlorm import sqlfunc

@sqlfunc
def tasks_completion_report(start_date, end_date):
    """SELECT done_at, COUNT(*) count
       FROM tasks
       WHERE done_at >= %(start_date)s AND done_at <= %(end_date)s
       GROUP BY done_at"""

In this example, start_date and end_date are parameters and will be properly escaped. Executing the function will run the SQL query in the active transaction.

Connections and transactions are used via context managers. The Engine class manages DB-API connections.

from sqlorm import Engine
import datetime

engine = Engine.from_uri("sqlite://app.db")

with engine:
    report = tasks_completion_report(datetime.date(2025, 1, 1), datetime.date.today())

SQLORM provides many utilities to help you build SQL statements, as well as fetch related rows in a single query.

Rows are returned as dicts by default, but you can hydrate objects instead:

class Task:
    pass

@sqlfunc(model=Task)
def find_tasks():
    "SELECT * FROM tasks"

with engine:
    tasks = find_tasks()

Now, we don't want to write endless simple statements to re-create basic CRUD functionality, so SQLORM provides a Model class. It follows the Active Record pattern.

from sqlorm import Model

class Task(Model):
    pass

with engine:
    tasks = Task.find_all()

    task = Task.create(title="my task")

    task = Task.find_one(id=1)
    task.done = True
    task.save()

Of course, model classes can have SQL methods !

class Task(Model):
    @classmethod
    def find_todos(cls):
        "SELECT * FROM tasks WHERE not done"

    def toggle(self):
        "UPDATE tasks SET done = not done WHERE id = %(self.id)s"

with engine:
    tasks = Task.find_todos()
    task = next(tasks)
    task.toggle()

As you've noticed, model classes do not need to know the columns in advance. However, it's good practice to define the columns for auto completion, type checking and DDL statements generation. SQLORM lets you do that using Python annotations:

from sqlorm import PrimaryKey

class Task(Model):
    id: PrimaryKey[int]
    title: str
    done: bool

Model classes provide many more utilities for handling relationships, lazy loading, column types, etc.

As mentioned earlier, model classes are not attached to any particular engine — they run on the engine provided by the current context. This makes it easy to implement patterns such as reading from a replica and writing to the primary.

We can implement reading from a replica and writing to the primary in a few lines:

main = Engine.from_uri("postgresql://main")
replica = Engine.from_uri("postgresql://replica")

with replica:
    task = Task.get(1)
    if not task.done:
        with main:
            task.toggle()

SQLORM has many more powerful features. It is well documented and provides an integration with Flask. Try it out !

联系我们 contact @ memedata.com