How can I use UUIDs in SQLAlchemy?

后端 未结 8 967
心在旅途
心在旅途 2020-12-07 10:58

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

相关标签:
8条回答
  • 2020-12-07 11:32

    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)
    
    0 讨论(0)
  • 2020-12-07 11:33

    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)
    
    0 讨论(0)
  • 2020-12-07 11:37

    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),
    )
    
    0 讨论(0)
  • 2020-12-07 11:40

    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(db.Integer, primary_key=True)
        id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True, nullable=False)
    

    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.

    0 讨论(0)
  • 2020-12-07 11:41

    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.

    0 讨论(0)
  • 2020-12-07 11:41

    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)
    
    0 讨论(0)
提交回复
热议问题