如何在SQLAlchemy中使用UUID?


94

如果使用PostgreSQL(Postgres),是否可以在SQLAlchemy中将列(主键)定义为UUID


2
不幸的是,SQLAlchemy文档中有关列类型的后端不可知GUID类型似乎不适用于SQLite数据库引擎中的主键。不像我希望的那样普遍。
adamek '04

SQLAlchemy的utils的提供UUIDType装饰,没有必要重新发明轮子
菲利普贝泽拉德索萨

Answers:


152

sqlalchemy postgres方言支持UUID列。这很容易(问题特别是postgres)-我不明白为什么其他答案都那么复杂。

这是一个例子:

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)

注意不要错过将传递callable uuid.uuid4到列定义中,而不要使用调用函数本身uuid.uuid4()。否则,该类的所有实例将具有相同的标量值。此处有更多详细信息:

表示此列默认值的标量,Python可调用或ColumnElement表达式,如果在插入的VALUES子句中未指定此列,则将在插入时调用该表达式。


6
我完全同意你的看法。其他一些答案对于其他数据库来说很酷,但是对于postgres来说,这是最干净的解决方案。(您也可以将默认设置为uuid.uuid4)。
pacha

1
您可以提供MWE吗?或者,flask_sqlalchemy中的序列化程序可以理解UUID类型?pastebin中的代码下方有错误,pastebin.com/hW8KPuYw
布兰登·

1
没关系,如果您想使用stdlib中的UUID对象,请这样做Column(UUID(as_uuid=True) ...)
布兰登·杜贝

1
谢谢!如果它可能是好的,Column并且Integer是在代码段的顶级进口,或改为阅读db.Columndb.Integer
格雷格Sadetsky

1
不需要@nephanth
Filipe Bezerra de Sousa

64

我写了这个,域名消失了,但是这里有胆量...

无论我的同事们如何真正关心正确的数据库设计,他们对用于关键字段的UUID和GUID的看法如何。我经常发现我需要这样做。我认为与自动增量相比,它具有一些优势,值得这样做。

在过去的几个月中,我一直在优化UUID列类型,我认为我终于使它变得可靠了。

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

我相信以二进制(16字节)存储应该比字符串表示(36字节?)更有效,而且似乎有迹象表明在mysql中索引16个字节的块应该比字符串更有效。我不希望它会变得更糟。

我发现的一个缺点是,至少在phpymyadmin中,您无法编辑记录,因为它隐式地尝试对“ select * from table id = ...的表”进行某种字符转换,并且存在其他显示问题。

除此之外,一切似乎都工作正常,所以我把它扔在那里。如果看到明显的错误,请发表评论。我欢迎任何改进建议。

除非我缺少什么,否则如果基础数据库具有UUID类型,则上述解决方案将起作用。否则,创建表时可能会出错。我想出的解决方案最初是针对MSSqlServer,然后最终成为MySql,所以我认为我的解决方案更灵活一些,因为它似乎可以在mysql和sqlite上正常工作。还没有打扰检查postgres。


是的,我看到雅各布的回答引荐了我,就把它发布了。
汤姆·威利斯

4
请注意,如果使用的是0.6或更高版本,则Tom解决方案中的MSBinary导入语句应更改为“ from sqlalchemy.dialects.mysql.base import MSBinary”。来源:mail-archive.com/sqlalchemy@googlegroups.com/msg18397.html
Cal Jacobson,2010年

2
“我写了这个”是一个死链接。
2013年


2
@codeninja postgresql已经具有本机UUID类型,因此直接使用sqlalchemy.dialects.postgresql.UUID即可。参见与后端无关的GUID类型
cowbert,2017年

24

如果您对具有UUID值的'String'列满意,则可以采用以下简单解决方案:

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

class MyTable(Base):
    __tablename__ = 'my_table'

    uuid = Column(String, name="uuid", primary_key=True, default=generate_uuid)

5
不要将UUID存储为字符串,除非您使用的是一个不支持它们的真正奇怪的数据库。否则,也许将所有数据存储为字符串...;)
Nick

@尼克为什么?不利之处是什么?
rayepps

6
@rayepps-有很多弊端-不在意:大小-字符串uuid占用两倍的空间-16bytes vs 32 chars-不包括任何格式化程序。处理时间-更多的字节=随着数据集变大,CPU需要更多的处理时间。uuid字符串格式因语言而异,因此添加了其他必需的翻译。某人更容易滥用该列,因为您可以在其中放置任何东西,这些不是uuid。这应该足以开始。
尼克

出于性能问题,您不应将字符串用作uuid的列。更推荐使用Binary(16)。
西里尔N.

19

我目前正在尝试使用此功能,问题是我得到一个错误: raise InvalidStatus("notfound: {k}. (cls={cls})".format(k=k, cls=cls)) alchemyjsonschema.InvalidStatus: notfound: BINARY(16). (cls=<class 'sqlalchemy_utils.types.uuid.UUIDType'>)
CodeTrooper

你们收到错误了NameError: name 'sqlalchemy_utils' is not defined吗?
沃尔特

1
SQLAlchemy-Utils是第三方软件包,您需要首先安装它:pip install sqlalchemy-utils
Berislav Lopac,

尽管您的迁移需要使用uuid的帐户或具有UUID vs CHAR / BINARY值的系统,但是这是要走的路。
rjurney19年

9

由于您使用的是Postgres,因此应该可以:

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)

1
对于那些使用PostgreSQL数据库的开发人员来说,这应该是唯一可接受的答案。
何塞L.帕蒂诺

5

这是一种基于SQLAlchemy文档的后端不可知GUID的方法,但是使用BINARY字段将UUID存储在非postgresql数据库中。

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)

1
它的用途是什么?
CodeTrooper

3

如果有人感兴趣,我一直在使用Tom Willis的答案,但是发现在process_bind_param方法中将字符串添加到uuid.UUID转换很有用。

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

-19

您可以尝试编写自定义类型,例如:

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的答案,还有此Blog条目。它看起来很相似,只不过是子类types.TypeDecorator而不是types.TypeEngine。哪种方法相对于另一种方法有优势还是劣势?
Jacob Gabrielson

11
这甚至不起作用,只是文档中虚拟类型示例中的剪切和粘贴作业。汤姆·威利斯(Tom Willis)在下面的回答要好得多。
Jesse Dhillon

它不需要default=?吗?例如Column('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)
iJames

链接指向“找不到页面”,docs.sqlalchemy.org
en/
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.