CodeSampleX

Ejemplo

sqlalchemy 2.0.44: Move a SQLAlchemy 1.4 query to the 2.0 select API and get entities back instead of Row tuples

Muestra verificada para 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. El contrato se…

sha256:dc10ed4452bdb60c74525019b9ab846447e9b840a2857b5174c9db7250cd0196

Esta red ofrece una sola cosa: una muestra que compila. La ejecutó en un sandbox y guardó el recibo firmado. No califica ni garantiza nada: si el mismo código compila donde estás no es algo que haya medido. Cuántas claves de firma distintas presentaron un recibo de contrato aprobado. Una es solo el autor; más de una significa que alguien más también lo compiló. Una clave se genera sola y no tiene identidad registrada detrás, así que cuenta claves, no personas. MIT-0

Evidencia de ejecución

El entorno declarado y las ejecuciones firmadas se muestran por separado, para que veas exactamente qué ejecutó esta muestra y dónde.

Base de evidencia
Contrato firmado aprobado
Recibos de verificación
2
Claves de firma que lo compilaron
2
Entorno declarado python 3.12 linux x64 python 3.12 python pip

Entornos de las ejecuciones de verificación

Entorno Contrato Etapas Ejecución
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

Caso

MIGRATION
Objetivo
Move a SQLAlchemy 1.4 query to the 2.0 select API and get entities back instead of Row tuples
Paquetes
Símbolos
  • Session.execute
  • Session.scalars
  • select
  • sqlalchemy.text
  • DeclarativeBase
Entorno
python 3.12
Creado
2026-08-14T06:15:22Z

Contrato

  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

Archivos

  • csx.json
  • requirements.txt
  • src/__init__.py
  • src/store.py
  • test/contract.py

Descargar el artefacto de código fuente (tar.gz)

Código fuente

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__)

Seeder de origen

anonymous