How can I use UUIDs in SQLAlchemy?

PythonPostgresqlSqlalchemy

Python Problem Overview


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

Python Solutions


Solution 1 - Python

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.

Solution 2 - Python

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.

Solution 3 - Python

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)

Solution 4 - Python

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

Solution 5 - Python

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)

Solution 6 - Python

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)

Solution 7 - Python

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

Solution 8 - Python

We can use UUIDType,

from sqlalchemy_utils import UUIDType
from sqlalchemy import VARCHAR,
class User(Base):
    __tablename__ = "user"
    id = Column(UUIDType(binary=False), primary_key=True, default=uuid.uuid4())
    name = Column(VARCHAR(length=30))
    first_name = Column(VARCHAR(length=30))
    last_name = Column(VARCHAR(length=30))

For more details we can refer to the official documentation.

Solution 9 - Python

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),
)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVasilView Question on Stackoverflow
Solution 1 - PythonJDiMatteoView Answer on Stackoverflow
Solution 2 - PythonTom WillisView Answer on Stackoverflow
Solution 3 - PythonKushal AhmedView Answer on Stackoverflow
Solution 4 - PythonBerislav LopacView Answer on Stackoverflow
Solution 5 - PythonGranatView Answer on Stackoverflow
Solution 6 - PythonzwirbeltierView Answer on Stackoverflow
Solution 7 - PythonNemethView Answer on Stackoverflow
Solution 8 - PythonAtul AnandView Answer on Stackoverflow
Solution 9 - PythonFlorian BöschView Answer on Stackoverflow