샘플
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. python 3.12 · linux…
sha256:dc10ed4452bdb60c74525019b9ab846447e9b840a2857b5174c9db7250cd0196
이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다.
통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다.
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__)