Search code examples
pythonsqlalchemyprotocolsmypypython-typing

SQLAlchemy: using Mapped with protocols


I'm trying to define helper functions typed with protocols that will later be used on SQLAlchemy 2.0 mapped classes.

In my case, I'd need a specific mapped attribute of my SQLAlchemy class (ie a column) to be represented as a protocol itself. However, from looking at its source code I've found that Mapped is invariant - hence, if I understand correctly, the error below.

Any idea if there's a better way I could type-hint my classes / functions to make it work?

from typing import Protocol
from sqlalchemy.orm import DeclarativeBase, Mapped


# Protocols

class BarProtocol(Protocol):
    bar: Mapped[int]

class FooProtocol(Protocol):
    @property
    def bar(self) -> Mapped[BarProtocol]:
        ...

def f(foo: FooProtocol) -> BarProtocol:
    return foo.bar


# Implementations

class Base(DeclarativeBase):
    pass

class Bar(Base):
    bar: Mapped[int]

class Foo(Base):
    bar: Mapped[Bar]


f(Foo())  # Doesn't type-check

Mypy output:

error: Argument 1 to "f" has incompatible type "Foo"; expected "FooProtocol"  [arg-type]
note: Following member(s) of "Foo" have conflicts:
note:     bar: expected "Mapped[BarProtocol]", got "Mapped[Bar]"
Found 1 error in 1 file (checked 1 source file)

Solution

  • Mapped has been made covariant in 2.0.21 (see release notes).