示例
sqlalchemy 2.0.44: Move a SQLAlchemy 1.4 query to the 2.0 select API and get entities back instead of Row tuples
已验证示例 — pypi sqlalchemy 2.0.44: Move a SQLAlchemy 1.4 query to the 2.0 select API and get entities back instead of Row tuples. contract 在 python 3.12 · linux…
sha256:dc10ed4452bdb60c74525019b9ab846447e9b840a2857b5174c9db7250cd0196
本网络只提供一件事:能构建的样本。它在沙箱中运行并保留签名回执。它不评级、不担保——同样的代码能否在你的环境构建,它没有测量过。
提交了通过的契约回执的不同签名密钥数量。为 1 表示只有作者;大于 1 表示还有其他人构建过。密钥是自行生成的,背后没有注册身份,因此计的是密钥而非人。
MIT-0
执行证据
声明的环境与签名的运行分开呈现,你可以看到这个样本究竟运行了什么、在哪里运行。
- 证据依据
- 签名契约通过
- 验证回执
- 2
- 构建过它的签名密钥
- 2
声明的环境
python 3.12 linux x64 python 3.12 python pip
验证运行环境
| 环境 | 契约 | 阶段 | 运行日期 |
|---|---|---|---|
| python 3.12 · linux alpine/x64 · docker ed25519:a2ec939a4c60e243 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · pypi@1 |
2026-08-14 |
| python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · pypi@1 |
2026-08-17 |
案例
MIGRATION- 目标
- Move a SQLAlchemy 1.4 query to the 2.0 select API and get entities back instead of Row tuples
- 符号
-
- Session.execute
- Session.scalars
- select
- sqlalchemy.text
- DeclarativeBase
- 环境
- python 3.12
- 创建时间
- 2026-08-14T06:15:22Z
契约
- assert session.execute(select(...)) returns Row tuples wrapping the entity
- assert scalars() unwraps them into entities
- assert the 1.x Query API still works and query().get() only warns
- assert Engine.execute is gone and a bare SQL string is not executable
- assert Connection and Session raise different exceptions for the same bare string
文件
- csx.json
- requirements.txt
- src/__init__.py
- src/store.py
- test/contract.py
源代码
{"case":{"caseId":"case:sha256:b0c05249719b4c637f99ca3e4886db933b3a88cf9ecdaffb3c658060f43263ea","constraints":{"runtime":"python"},"contract":["assert session.execute(select(...)) returns Row tuples wrapping the entity","assert scalars() unwraps them into entities","assert the 1.x Query API still works and query().get() only warns","assert Engine.execute is gone and a bare SQL string is not executable","assert Connection and Session raise different exceptions for the same bare string"],"goal":"Move a SQLAlchemy 1.4 query to the 2.0 select API and get entities back instead of Row tuples","kind":"MIGRATION","packages":["pkg:pypi/sqlalchemy@2.0.44"],"schemaVersion":1,"symbols":["Session.execute","Session.scalars","select","sqlalchemy.text","DeclarativeBase"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","runtimeVersion":"3.12","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/sqlalchemy@2.0.44"],"schemaVersion":1,"symbols":["Session.execute","Session.scalars","select","sqlalchemy.text","DeclarativeBase"],"verifierAdapter":"pypi@1"}
sqlalchemy==2.0.44
typing-extensions==4.16.0
greenlet==3.2.4
"""SQLAlchemy 1.4 -> 2.0, in the two places it actually costs time.
The first is quiet. session.execute(select(User)) does not give you User
objects — it gives you Row tuples with a User inside each one, exactly like
a SQL driver would. Code written against Query gets a list of one-element
tuples, and the failure surfaces later as an attribute error on a tuple.
.scalars() is the unwrapping step, and it is the whole difference between
the two idioms.
The second is loud but easy to misread. A bare SQL string is no longer
executable, and the exception depends on where you passed it: a Connection
says ObjectNotExecutableError, a Session says ArgumentError. Both mean
wrap it in text().
What did NOT change, measured rather than assumed: Query still works, and
session.query(User).get(1) warns instead of failing. So a 2.0 upgrade can
run green while none of the code is 2.0 yet.
"""
import sqlalchemy as sa
from sqlalchemy import select
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
def seeded_engine() -> sa.Engine:
engine = sa.create_engine("sqlite+pysqlite:///:memory:")
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add_all([User(id=1, name="ada"), User(id=2, name="linus")])
session.commit()
return engine
def rows(session: Session) -> list:
"""The 2.0 call that surprises people: Row tuples, not entities."""
return session.execute(select(User)).all()
def entities(session: Session) -> list[User]:
"""The same query, unwrapped. This is what Query used to return."""
return session.scalars(select(User)).all()
import sys
import warnings
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import sqlalchemy as sa
from sqlalchemy import select
from sqlalchemy.orm import Session
from src.store import User, entities, rows, seeded_engine
engine = seeded_engine()
with Session(engine) as session:
# execute() returns Row tuples that WRAP the entity.
first = rows(session)[0]
assert type(first).__name__ == "Row", type(first)
assert isinstance(first[0], User), first
assert not isinstance(first, User)
# scalars() unwraps them. This is the line most migrations are missing.
people = entities(session)
assert [u.name for u in people] == ["ada", "linus"], people
# The 1.x Query API was not removed, which is why an upgrade can look
# finished before it has started.
assert [u.name for u in session.query(User).all()] == ["ada", "linus"]
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
session.query(User).get(1)
assert [type(c.message).__name__ for c in caught] == ["LegacyAPIWarning"], caught
# session.get is the replacement, and it returns the entity directly.
assert session.get(User, 1).name == "ada"
assert session.scalars(select(User).where(User.id == 99)).one_or_none() is None
# Connectionless execution really is gone: this one is an AttributeError,
# not a deprecation.
assert not hasattr(engine, "execute")
# A bare SQL string is not executable, and the two entry points disagree
# about which exception says so.
with engine.connect() as conn:
try:
conn.execute("select 1")
raise AssertionError("a bare string should not be executable")
except sa.exc.ObjectNotExecutableError:
pass
assert list(conn.execute(sa.text("select 1"))) == [(1,)]
with Session(engine) as session:
try:
session.execute("select 1")
raise AssertionError("a bare string should not be executable")
except sa.exc.ArgumentError as exc:
assert "text(" in str(exc), exc
print("contract ok:", sa.__version__)