CodeSampleX

Пример

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. Контракт выполнен…

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

Контракт

  1. assert session.execute(select(...)) returns Row tuples wrapping the entity
  2. assert scalars() unwraps them into entities
  3. assert the 1.x Query API still works and query().get() only warns
  4. assert Engine.execute is gone and a bare SQL string is not executable
  5. 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

Скачать артефакт с исходным кодом (tar.gz)

Исходный код

csx.json
{"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"}
requirements.txt
sqlalchemy==2.0.44
typing-extensions==4.16.0
greenlet==3.2.4
src/__init__.py
src/store.py
"""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()
test/contract.py
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__)

Исходный сидер

anonymous