PostgreSQL UUID type, Python’s UUID — and type hints

1,396 views
Skip to first unread message

jens.t...@gmail.com

unread,
Jan 12, 2022, 9:35:48 PM1/12/22
to sqlalchemy
Hello,

For a PostgreSQL 14 db, I defined a User mapping and helper function like so:

from sqlalchemy.dialects.postgresql import UUID

class User(Base):
    id = Column(UUID(as_uuid=True), primary_key=True, server_default=func.gen_random_uuid())
    name = Column(Unicode(128))

    @staticmethod
    def create(dbsession: Session, name: str, id_: uuid.UUID = None):
        user = User(name=name, id=id_)
        dbsession.add(user)
        return user

Notice that the helper function’s id_ has a different UUID type than the User’s mapped property. And that, in return causes this mypy error:

error: Argument "id_" to "create" of "User" has incompatible type "uuid.UUID"; expected "Optional[sqlalchemy.dialects.postgresql.base.UUID]"  [arg-type]

I looked into the implementation of the dialect’s UUID whether it implements a constructor which takes a Python UUID, but no luck. Given that PostgreSQL supports UUIDs natively, I didn’t want to use UUIDType either. So, Other that cast() I’m not sure how to work with this error…

Thanks!
Jens

Mike Bayer

unread,
Jan 13, 2022, 8:52:18 AM1/13/22
to noreply-spamdigest via sqlalchemy
is this with the SQLAlchemy Mypy plugin?   current status is for 2.0 we are looking to move away from the plugin model and pretty much change how these things work.    Otherwise  if this is with the plugin, you would use "id: Mapped[uuid.UUID] = ..."
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
 
 
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+...@googlegroups.com.

jens.t...@gmail.com

unread,
Jan 15, 2022, 3:34:23 PM1/15/22
to sqlalchemy
Hello Mike,

Yes that’s using the plugin: sqlalchemy[mypy,postgresql_psycopg2binary]==1.4.29 I changed the function declaration to this:

    @staticmethod
    def create(dbsession: Session, name: str, id_: typing.Union[uuid.UUID, sqlalchemy.dialects.postgresql.base.UUID] = None):

and it seems to work. In a similar vain, how do I manage the types of mapped and unmapped objects? Following the above example:

user = User(dbsession, "Joe Black")

returns an unmapped, simple User object. Now suppose I have another existing, mapped User object and would like to assign that  newly created unmapped user:

other_user.spouse = user

which creates the following error:

error: Argument "spouse" to "User" has incompatible type "User"; expected "Mapped[Any]"  [arg-type]

Declaring that column with:

spouse: typing.Union[Mapped[User], User] = relationship("User", back_populates="spouse", uselist=False)

seems to work; I just want to make sure that that’s a recommended way of going about this? Should I always declare a type as a Union between a mapped and unmapped same class?

Other than following the migration guide, I haven’t looked into SQLA2.
Jens

Mike Bayer

unread,
Jan 16, 2022, 10:04:46 AM1/16/22
to noreply-spamdigest via sqlalchemy


On Sat, Jan 15, 2022, at 3:34 PM, jens.t...@gmail.com wrote:
Hello Mike,

Yes that’s using the plugin: sqlalchemy[mypy,postgresql_psycopg2binary]==1.4.29 I changed the function declaration to this:

    @staticmethod
    def create(dbsession: Session, name: str, id_: typing.Union[uuid.UUID, sqlalchemy.dialects.postgresql.base.UUID] = None):

and it seems to work. In a similar vain, how do I manage the types of mapped and unmapped objects? Following the above example:

user = User(dbsession, "Joe Black")

returns an unmapped, simple User object. Now suppose I have another existing, mapped User object and would like to assign that  newly created unmapped user:

other_user.spouse = user

which creates the following error:

error: Argument "spouse" to "User" has incompatible type "User"; expected "Mapped[Any]"  [arg-type]

if the User class is unmapped, you wouldn't be assigning it to a relationship attribute like that.

I think what you might mean is that you have a User object that is what we call "transient", meaning, it's not yet persisted with any Session.  

again, you would want to set up the relationship attribute with a more specific annotation:

class User(...):
    # ...
    spouse: Mapped[User] = relationship("User", ...)




Declaring that column with:

spouse: typing.Union[Mapped[User], User] = relationship("User", back_populates="spouse", uselist=False)
seems to work; I just want to make sure that that’s a recommended way of going about this? Should I always declare a type as a Union between a mapped and unmapped same class?

no that's not correct.   The User class is "mapped" at the class level.   you would only need one directive or the other (one of Mapped[User] or User) when using the Mypy plugin.






Other than following the migration guide, I haven’t looked into SQLA2.
Jens


On Thursday, January 13, 2022 at 11:52:18 PM UTC+10 Mike Bayer wrote:

is this with the SQLAlchemy Mypy plugin?   current status is for 2.0 we are looking to move away from the plugin model and pretty much change how these things work.    Otherwise  if this is with the plugin, you would use "id: Mapped[uuid.UUID] = ..."


--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
 
 
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+...@googlegroups.com.

jens.t...@gmail.com

unread,
Jan 24, 2022, 11:32:41 PM1/24/22
to sqlalchemy
Mike, thank you for elaborating, that helps a lot! Yes, I meant a transient or pending object above, you’re correct.

To make sure I understand correctly: it’s ok to declare

    deleted_at: Mapped[Optional[datetime.datetime]] = Column(sqlalchemy.DateTime, nullable=True)

 or 

    id: Mapped[uuid.UUID] = Column(sqlalchemy.dialects.postgresql.UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)

where the RHS declares the column’s type by means of a SQLA type and the LHS’s type a “mapped” stdlib Python type? It works here because the column’s type actually is implemented by means of the LHS stdlib type. I also noticed that I can omit e.g. Mapped[uuid.UUID] and in some cases mypy is still able to infer the correct type.

Much thanks,
Jens

Mike Bayer

unread,
Jan 25, 2022, 1:06:03 AM1/25/22
to noreply-spamdigest via sqlalchemy


On Mon, Jan 24, 2022, at 11:32 PM, jens.t...@gmail.com wrote:
Mike, thank you for elaborating, that helps a lot! Yes, I meant a transient or pending object above, you’re correct.

To make sure I understand correctly: it’s ok to declare

    deleted_at: Mapped[Optional[datetime.datetime]] = Column(sqlalchemy.DateTime, nullable=True)

 or 

    id: Mapped[uuid.UUID] = Column(sqlalchemy.dialects.postgresql.UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)


where the RHS declares the column’s type by means of a SQLA type and the LHS’s type a “mapped” stdlib Python type?

yes, the above syntax is what you can use specifically when the sqlalchemy mypy plugin is installed, because it will sneak inside the mypy state and hide those "Column" objects from causing typing errors.

if you aren't using the plugin, then the above won't work.   I'm working on a new version of declarative that changes the above slightly so that you can do something basically exactly the same as the above (in a structural sense, there's a bit of an in-place naming change) without having to use a mypy plugin.


It works here because the column’s type actually is implemented by means of the LHS stdlib type. I also noticed that I can omit e.g. Mapped[uuid.UUID] and in some cases mypy is still able to infer the correct type.

In this case, SQLAlchemy's mypy plugin does the inference in this case based on hardcoded rules that inspect the Column objects.   


jens.t...@gmail.com

unread,
Jan 26, 2022, 2:50:34 AM1/26/22
to sqlalchemy
Mike, I really appreciate your responses, thank you! All makes sense 🤓
Jens
Reply all
Reply to author
Forward
0 new messages