165

Is there a way to define a column (primary key) as a UUID in SQLAlchemy if using PostgreSQL (Postgres)?

Braiam
  • 1
  • 11
  • 47
  • 78
Vasil
  • 36,468
  • 26
  • 90
  • 114
  • 2
    Unfortunately [Backend-agnostic GUID Type](http://docs.sqlalchemy.org/en/rel_0_9/core/custom_types.html?highlight=guid#backend-agnostic-guid-type) from the SQLAlchemy documentation for column types does not seem to work for primary keys in SQLite database engines. Not quite as ecumenical as I was hoping for. – adamek Apr 24 '12 at 16:04
  • 1
    SQLAlchemy utils provides [UUIDType decorator](https://sqlalchemy-utils.readthedocs.io/en/latest/_modules/sqlalchemy_utils/types/uuid.html), there's no need to reinvent the wheel – Filipe Bezerra de Sousa Aug 06 '20 at 16:11
  • @FilipeBezerradeSousa One caveat is that you might need to change `value.hex` in the line `return value.bytes if self.binary else value.hex` in the [UUIDType decorator source](https://sqlalchemy-utils.readthedocs.io/en/latest/_modules/sqlalchemy_utils/types/uuid.html) to `str(value)`, otherwise comparing to a UUID literal object inside `.where()` will fail. E.g., without this change, a query such as `select(table.c).where(table.c.uuid == UUID("bb89641d-64c4-4433-975a-0085d737a5fb"))`. (Assuming there actually is a row with such a UUID.) – natskvi Mar 09 '22 at 20:13

11 Answers11

317

The sqlalchemy postgres dialect supports UUID columns. This is easy (and the question is specifically postgres) -- I don't understand why the other answers are all so complicated.

Here is an example:

from sqlalchemy.dialects.postgresql import UUID
from flask_sqlalchemy import SQLAlchemy
import uuid

db = SQLAlchemy()

class Foo(db.Model):
    id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)

Be careful not to miss passing the callable uuid.uuid4 into the column definition, rather than calling the function itself with uuid.uuid4(). Otherwise, you will have the same scalar value for all instances of this class. More details here:

A scalar, Python callable, or ColumnElement expression representing the default value for this column, which will be invoked upon insert if this column is otherwise not specified in the VALUES clause of the insert.

Stephen Fuhry
  • 12,624
  • 6
  • 56
  • 55
JDiMatteo
  • 12,022
  • 5
  • 54
  • 65
  • 12
    I totally agree with you. Some of the other answers are cool for other databases, but for postgres this is the cleanest solution. (You can also set a default as `uuid.uuid4`). – pacha Apr 10 '18 at 16:38
  • In my view, this is by far the best answer here. The top voted one builds on the MySQL dialect ... – sandris May 09 '18 at 13:19
  • 1
    Can you provide a MWE? Or maybe the serializer in flask_sqlalchemy understands the UUID type? The code in the pastebin below errors, https://pastebin.com/hW8KPuYw – Brandon Dube May 30 '18 at 05:02
  • 1
    nevermind, if you want to use UUID objects from stdlib, do `Column(UUID(as_uuid=True) ...)` – Brandon Dube May 30 '18 at 05:05
  • 1
    Thank you! It might be nice if `Column` and `Integer` were imported at the top of the code snippet, or were changed to read `db.Column` and `db.Integer` – Greg Sadetsky Sep 14 '18 at 14:23
  • Postgres seems to support UUID natively. if `as_uuid` is set to True, when a result from the db is processed the code tries to execute something like `uuid.UUID(uuid.uuid4())` which it raise an exception. Then, more care has to be taken to set `as_uuid`. – Jorge E. Cardona Jun 03 '19 at 11:19
  • 1
    is it necessary to specify ``nullable=False`` when you’ve already set ``primary_key=True`` ? – tbrugere Jun 29 '20 at 16:13
  • 2
    No there's no need @nephanth – Filipe Bezerra de Sousa Aug 06 '20 at 16:13
  • Does the `default` callable get run when an instance of class is created, or when it's committed to the database? – theo-brown Sep 28 '21 at 10:22
70

I wrote this and the domain is gone but here's the guts....

Regardless of how my colleagues who really care about proper database design feel about UUID's and GUIDs used for key fields. I often find I need to do it. I think it has some advantages over autoincrement that make it worth it.

I've been refining a UUID column type for the past few months and I think I've finally got it solid.

from sqlalchemy import types
from sqlalchemy.dialects.mysql.base import MSBinary
from sqlalchemy.schema import Column
import uuid


class UUID(types.TypeDecorator):
    impl = MSBinary
    def __init__(self):
        self.impl.length = 16
        types.TypeDecorator.__init__(self,length=self.impl.length)

    def process_bind_param(self,value,dialect=None):
        if value and isinstance(value,uuid.UUID):
            return value.bytes
        elif value and not isinstance(value,uuid.UUID):
            raise ValueError,'value %s is not a valid uuid.UUID' % value
        else:
            return None

    def process_result_value(self,value,dialect=None):
        if value:
            return uuid.UUID(bytes=value)
        else:
            return None

    def is_mutable(self):
        return False


id_column_name = "id"

def id_column():
    import uuid
    return Column(id_column_name,UUID(),primary_key=True,default=uuid.uuid4)

# Usage
my_table = Table('test',
         metadata,
         id_column(),
         Column('parent_id',
            UUID(),
            ForeignKey(table_parent.c.id)))

I believe storing as binary(16 bytes) should end up being more efficient than the string representation(36 bytes?), And there seems to be some indication that indexing 16 byte blocks should be more efficient in mysql than strings. I wouldn't expect it to be worse anyway.

One disadvantage I've found is that at least in phpymyadmin, you can't edit records because it implicitly tries to do some sort of character conversion for the "select * from table where id =..." and there's miscellaneous display issues.

Other than that everything seems to work fine, and so I'm throwing it out there. Leave a comment if you see a glaring error with it. I welcome any suggestions for improving it.

Unless I'm missing something the above solution will work if the underlying database has a UUID type. If it doesn't, you would likely get errors when the table is created. The solution I came up with I was targeting MSSqlServer originally and then went MySql in the end, so I think my solution is a little more flexible as it seems to work fine on mysql and sqlite. Haven't bothered checking postgres yet.

Tom Willis
  • 5,250
  • 23
  • 34
  • yeah I posted it after I saw referrals from Jacob's answer. – Tom Willis Aug 03 '10 at 02:10
  • 4
    Note that if you're using version 0.6 or greater, the MSBinary import statement in Tom's solution should be changed to "from sqlalchemy.dialects.mysql.base import MSBinary". Source: http://www.mail-archive.com/sqlalchemy@googlegroups.com/msg18397.html – Cal Jacobson Sep 24 '10 at 23:04
  • 2
    "I wrote this" is a dead link. – julx Jun 18 '13 at 14:40
  • 3
    See also [the UUIDType that ships with SQLAlchemy-utils](http://sqlalchemy-utils.readthedocs.org/en/latest/data_types.html#module-sqlalchemy_utils.types.uuid) – floer32 Jul 16 '14 at 17:20
  • How do you set this up so you can compare for equality against a UUID string? Is it enough to override `__eq__`? E.g. when comparing against a string provided in a REST request: `if user.id == user_id: ...` – z0r Jun 28 '15 at 10:51
  • The comment of @CalJacobson is very useful; I propose to adjust this in the example code – Joost Döbken Mar 03 '16 at 08:56
  • i updated the example to reflect sqlalchemy's updates – Tom Willis Mar 22 '16 at 13:08
  • How would one target postgresql? – CodeTrooper Nov 13 '17 at 22:42
  • 2
    @codeninja postgresql already has a native UUID type, so just use `sqlalchemy.dialects.postgresql.UUID` directly. see [Backend-agnostic GUID Type](http://docs.sqlalchemy.org/en/latest/core/custom_types.html#backend-agnostic-guid-type) – cowbert Nov 20 '17 at 21:02
  • also, as of python 2.6, instead of invoking `TypeDecorator.__init__()` I believe you should call `super(UUID, self).__init__()`. (But it's possible that `TypeDecorator` was not a new-style class in back in 2010). – cowbert Nov 28 '17 at 00:31
44

If you are happy with a 'String' column having UUID value, here goes a simple solution:

def generate_uuid():
    return str(uuid.uuid4())

class MyTable(Base):
    __tablename__ = 'my_table'

    uuid = Column(String, name="uuid", primary_key=True, default=generate_uuid)
Razzi Abuissa
  • 3,337
  • 2
  • 28
  • 29
Kushal Ahmed
  • 473
  • 4
  • 2
  • 9
    Don't store UUID as string unless you are using a really weird database that doesn't support them. otherwise, maybe store all of your data as strings... ;) – Nick Apr 13 '18 at 18:29
  • @Nick why? whats the downside? – rayepps Jan 24 '20 at 18:54
  • 13
    @rayepps - there are many downsides - a few off the top of mind: size - string uuid takes up twice the space - 16bytes vs 32 chars - not including any formatters. Processing time - more bytes = more processing time by the CPU as your dataset gets bigger. uuid string formats differ by language, adding addition required translations. Easier for someone to misuse the column, as you can put anything in there, things that aren't uuids. That should be enough to start. – Nick Jan 25 '20 at 02:22
  • 2
    You should not use Strings as columns for a uuid, for performance issues. A Binary(16) is more recommended. – Cyril N. Nov 04 '20 at 09:17
  • 4
    Does sqlite qualify as a "really weird database"? :P – pdoherty926 May 28 '21 at 15:12
  • I am storing thousands strings as data, having one more field as string doesn't scare me. – m3nda Aug 06 '22 at 03:33
26

I've used the UUIDType from the SQLAlchemy-Utils package.

Maicon Mauricio
  • 2,052
  • 1
  • 13
  • 29
Berislav Lopac
  • 16,656
  • 6
  • 71
  • 80
  • I'm currently trying to use this, problem is I get an error: `raise InvalidStatus("notfound: {k}. (cls={cls})".format(k=k, cls=cls))` `alchemyjsonschema.InvalidStatus: notfound: BINARY(16). (cls=)` – CodeTrooper Nov 13 '17 at 21:35
  • Have you guys received the error: `NameError: name 'sqlalchemy_utils' is not defined`? –  Jan 05 '19 at 20:54
  • 1
    `SQLAlchemy-Utils` is a third-party package, you need to install it first: `pip install sqlalchemy-utils` – Berislav Lopac Jan 06 '19 at 17:58
  • This is the way to go, although your migrations need to account or systems that have UUID vs CHAR/BINARY values for uuids. – rjurney Jun 13 '19 at 19:36
18

Since you're using Postgres this should work:

from app.main import db
from sqlalchemy.dialects.postgresql import UUID

class Foo(db.Model):
    id = db.Column(UUID(as_uuid=True), primary_key=True)
    name = db.Column(db.String, nullable=False)
Granat
  • 191
  • 1
  • 4
7

Here is an approach based on the Backend agnostic GUID from the SQLAlchemy docs, but using a BINARY field to store the UUIDs in non-postgresql databases.

import uuid

from sqlalchemy.types import TypeDecorator, BINARY
from sqlalchemy.dialects.postgresql import UUID as psqlUUID

class UUID(TypeDecorator):
    """Platform-independent GUID type.

    Uses Postgresql's UUID type, otherwise uses
    BINARY(16), to store UUID.

    """
    impl = BINARY

    def load_dialect_impl(self, dialect):
        if dialect.name == 'postgresql':
            return dialect.type_descriptor(psqlUUID())
        else:
            return dialect.type_descriptor(BINARY(16))

    def process_bind_param(self, value, dialect):
        if value is None:
            return value
        else:
            if not isinstance(value, uuid.UUID):
                if isinstance(value, bytes):
                    value = uuid.UUID(bytes=value)
                elif isinstance(value, int):
                    value = uuid.UUID(int=value)
                elif isinstance(value, str):
                    value = uuid.UUID(value)
        if dialect.name == 'postgresql':
            return str(value)
        else:
            return value.bytes

    def process_result_value(self, value, dialect):
        if value is None:
            return value
        if dialect.name == 'postgresql':
            return uuid.UUID(value)
        else:
            return uuid.UUID(bytes=value)
zwirbeltier
  • 867
  • 9
  • 27
  • 1
    What would the usage of this be? – CodeTrooper Nov 13 '17 at 22:41
  • One scenario where you may want to support two different implementations of UUID (or any other type) would be when you are using one DB for production and one for tests. For instance, UUID in postgresql is a native type, but when writing tests you may use an in memomry sqlite DB that does not have the native UUID and needs the fallback. – Meir Feb 28 '23 at 19:46
4

In case anyone is interested, I've been using Tom Willis answer, but found useful to add a string to uuid.UUID conversion in the process_bind_param method

class UUID(types.TypeDecorator):
    impl = types.LargeBinary

    def __init__(self):
        self.impl.length = 16
        types.TypeDecorator.__init__(self, length=self.impl.length)

    def process_bind_param(self, value, dialect=None):
        if value and isinstance(value, uuid.UUID):
            return value.bytes
        elif value and isinstance(value, basestring):
            return uuid.UUID(value).bytes
        elif value:
            raise ValueError('value %s is not a valid uuid.UUId' % value)
        else:
            return None

    def process_result_value(self, value, dialect=None):
        if value:
            return uuid.UUID(bytes=value)
        else:
            return None

    def is_mutable(self):
        return False
Nemeth
  • 1,070
  • 1
  • 11
  • 16
3

I encountered the same issue, this should work, it works for me:

from sqlalchemy import Column, text
from sqlalchemy.dialects.postgresql import UUID

Column(
    "id", UUID(as_uuid=True),
    primary_key=True,
    server_default=text("gen_random_uuid()"),
)

If you use PostgreSQL < 14, I think you need to add this EXTENSION pack:

CREATE EXTENSION IF NOT EXISTS "pgcrypto";

You can use uuid_generate_v4() as well, you'd need to add the EXTENSION pack then:

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
gentiand
  • 31
  • 3
  • As of 2022, I can confirm that this works as expected with PostgreSQL if one wants a database-level solution, rather than generating uuid with Python. Are we forced to use a text based fallback, or is there a higher level SQLAlchemy API that supports this? – jbmusso Nov 18 '22 at 13:06
3

SQLAlchemy 2.0 adds the UUID type, the SQL-native form of the database agnostic type which is backwards compatible with the previous PostgreSQL-only version of UUID.

Example:

import sqlalchemy as sa
from sqlalchemy.orm import DeclarativeBase, Mapped

class Base(DeclarativeBase):
    pass

class MyModel(Base):
    my_field: Mapped[sa.UUID]
congusbongus
  • 13,359
  • 7
  • 71
  • 99
0

We can use UUIDType,

from sqlalchemy_utils import UUIDType
from sqlalchemy import String

class User(Base):
    id = Column(UUIDType(binary=False), primary_key=True, default=uuid.uuid4)
    name = Column(String)

For more details we can refer to the official documentation.

bfontaine
  • 18,169
  • 13
  • 73
  • 107
Atul Anand
  • 19
  • 2
-21

You could try writing a custom type, for instance:

import sqlalchemy.types as types

class UUID(types.TypeEngine):
    def get_col_spec(self):
        return "uuid"

    def bind_processor(self, dialect):
        def process(value):
            return value
        return process

    def result_processor(self, dialect):
        def process(value):
            return value
        return process

table = Table('foo', meta,
    Column('id', UUID(), primary_key=True),
)
Florian Bösch
  • 27,420
  • 11
  • 48
  • 53
  • 11
    This doesn't even work, it's just a cut-and-paste job from the dummy type example from the docs. Tom Willis' answer below is much better. – Jesse Dhillon Aug 02 '10 at 00:03
  • In addition to [Florian's answer](http://stackoverflow.com/questions/183042/how-can-i-use-uuids-in-sqlalchemy/188427#188427), there's also [this blog entry](http://blog.sadphaeton.com/2009/01/19/sqlalchemy-recipeuuid-column.html). It looks similar except that it subclasses `types.TypeDecorator` instead of `types.TypeEngine`. Does either approach have an advantage or disadvantage over the other one? – Jacob Gabrielson Apr 30 '09 at 19:35
  • Doesn't it need a `default=?`? e.g. `Column('id', UUID(), primary_key=True, default=)` – iJames May 13 '16 at 03:36
  • Link points to "Page not found", https://docs.sqlalchemy.org/en/13/core/custom_types.html#creating-new-types is propably close to the old one – barbsan Sep 13 '19 at 07:55