|
10 | 10 | from sqlalchemy.dialects.postgresql import UUID |
11 | 11 | from sqlalchemy.exc import StatementError |
12 | 12 | from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine |
13 | | -from sqlalchemy.orm import DeclarativeBase, sessionmaker |
| 13 | +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, sessionmaker |
14 | 14 |
|
15 | 15 |
|
16 | 16 | # Skip tests if database is not available (check for CI environment or explicit flag) |
@@ -357,6 +357,60 @@ async def test_uuid_select_full_row(self, session): |
357 | 357 | assert isinstance(row.uid, uuid.UUID) |
358 | 358 | assert row.uid == test_uuid |
359 | 359 |
|
| 360 | + async def test_uuid_mapped_annotation_without_explicit_as_uuid( |
| 361 | + self, engine |
| 362 | + ): |
| 363 | + """Test that Mapped[uuid.UUID] without explicit UUID(as_uuid=True) returns uuid.UUID objects. |
| 364 | +
|
| 365 | + This test verifies the exact pattern: |
| 366 | + id: Mapped[uuid.UUID] = mapped_column(primary_key=True) |
| 367 | +
|
| 368 | + When using SQLAlchemy 2.0+ Mapped annotations with Python's uuid.UUID type, |
| 369 | + the dialect should automatically return uuid.UUID objects, not strings. |
| 370 | + """ |
| 371 | + from sqlalchemy import select |
| 372 | + |
| 373 | + # Create a model using the Mapped[uuid.UUID] annotation pattern |
| 374 | + class SimplifiedUUIDModel(Base): |
| 375 | + __tablename__ = "test_simplified_uuid" |
| 376 | + |
| 377 | + id: Mapped[uuid.UUID] = mapped_column(primary_key=True) |
| 378 | + name: Mapped[str] = mapped_column(String(100)) |
| 379 | + |
| 380 | + # Create table |
| 381 | + async with engine.begin() as conn: |
| 382 | + await conn.run_sync( |
| 383 | + SimplifiedUUIDModel.__table__.create, checkfirst=True |
| 384 | + ) |
| 385 | + |
| 386 | + try: |
| 387 | + # Create session |
| 388 | + async_session = sessionmaker(engine, class_=AsyncSession) |
| 389 | + async with async_session() as session: |
| 390 | + # Insert test data |
| 391 | + test_uuid = uuid.uuid4() |
| 392 | + obj = SimplifiedUUIDModel(id=test_uuid, name="test") |
| 393 | + session.add(obj) |
| 394 | + await session.commit() |
| 395 | + |
| 396 | + # Select using the exact pattern from the issue |
| 397 | + stmt = select(SimplifiedUUIDModel.id) |
| 398 | + result = (await session.scalars(stmt)).all() |
| 399 | + |
| 400 | + # Critical assertion: should be uuid.UUID, not str |
| 401 | + assert len(result) == 1 |
| 402 | + assert isinstance(result[0], uuid.UUID), ( |
| 403 | + f"Expected uuid.UUID but got {type(result[0]).__name__}. " |
| 404 | + f"This means the Mapped[UUID] pattern is returning strings instead of UUID objects." |
| 405 | + ) |
| 406 | + assert result[0] == test_uuid |
| 407 | + finally: |
| 408 | + # Clean up |
| 409 | + async with engine.begin() as conn: |
| 410 | + await conn.run_sync( |
| 411 | + SimplifiedUUIDModel.__table__.drop, checkfirst=True |
| 412 | + ) |
| 413 | + |
360 | 414 |
|
361 | 415 | class TestUUIDTypeCompatibility: |
362 | 416 | """Test UUID type compatibility with existing functionality.""" |
|
0 commit comments