get_or_create with SQLAlchemy 2's ORM
Published 33 minutes ago.
I've long loved Django's get_or_create ORM function. If you're not familiar, typical usage looks something like this:
from django.contrib.auth.models import User
user, created = User.objects.get_or_create(
    username="johndoe", 
    defaults={"email": "john.doe@example.com"}
)
The above code is pretty self-explanatory: if a user with the username
johndoe exists, return it, otherwise create it with an email of
john.doe@example.com. It's concise and beautiful.
I've had to use SQLAlchemy 2 recently and I was surprised to find that it didn't have the same and examples I found, only worked for SQLAlchemy 1.x.
Here's how you can implement this function with SQLAlchemy 2:
import typing as T
import sqlalchemy as sa
from sqlalchemy.orm import Session
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
    __abstract__ = True
    @classmethod
    def get_or_create(
        cls, session: Session, defaults=None, commit=True, **kwargs
    ) -> T.Tuple[T.Self, bool]:
        """Django-inspired get_or_create."""
        predicates = [getattr(cls, k) == v for k, v in kwargs.items()]
        instance = session.scalar(sa.select(cls).where(*predicates))
        if instance:
            return instance, False
        defaults = defaults or {}
        instance_kwargs = kwargs | defaults
        instance = cls(**instance_kwargs)
        session.add(instance)
        if commit:
            session.commit()
        return instance, True
class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(primary_key=True)
    first_name: Mapped[str] = mapped_column(sa.Text, nullable=False)
    last_name: Mapped[str] = mapped_column(sa.Text, nullable=False)
    email: Mapped[str] = mapped_column(sa.Text, nullable=False)
engine = sa.create_engine("...")
make_session = sessionmaker(bind=engine)
with make_session() as session:
    user, created = User.get_or_create(
        session,
        email="john.doe@example.com",
        defaults={
          "first_name": "John",
          "last_name": "Doe"
        }
    )
    # created will be True, and the change will be committed to the database
Boo ya.