如何在 SQLAlchemy 中使用 UUID?
Posted
技术标签:
【中文标题】如何在 SQLAlchemy 中使用 UUID?【英文标题】:How can I use UUIDs in SQLAlchemy? 【发布时间】:2010-09-16 00:45:22 【问题描述】:如果使用PostgreSQL (Postgres),有没有办法在SQLAlchemy 中将列(主键)定义为UUID?
【问题讨论】:
不幸的是,列类型的 SQLAlchemy 文档中的 Backend-agnostic GUID Type 似乎不适用于 SQLite 数据库引擎中的主键。不像我希望的那样普遍。 SQLAlchemy utils 提供UUIDType decorator,无需重新发明*** 【参考方案1】: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(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
注意不要错过将callable
uuid.uuid4
传递到列定义中,而不是使用uuid.uuid4()
调用函数本身。否则,对于此类的所有实例,您将拥有相同的标量值。更多详情here:
表示此列默认值的标量、Python 可调用或 ColumnElement 表达式,如果在插入的 VALUES 子句中未指定此列,则将在插入时调用。
【讨论】:
我完全同意你的看法。其他一些答案对于其他数据库来说很酷,但对于 postgres,这是最干净的解决方案。 (您也可以将默认设置为uuid.uuid4
)。
您能提供 MWE 吗?或者,flask_sqlalchemy 中的序列化程序可能理解 UUID 类型?以下 pastebin 中的代码出错,pastebin.com/hW8KPuYw
没关系,如果您想使用 stdlib 中的 UUID 对象,请执行 Column(UUID(as_uuid=True) ...)
谢谢!如果 Column
和 Integer
在代码 sn-p 的顶部导入,或者改为阅读 db.Column
和 db.Integer
可能会很好
不,不需要@nephanth【参考方案2】:
I wrote this 域名消失了,但胆子在这里....
无论我真正关心正确数据库设计的同事如何看待用于关键字段的 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 where id =...”进行某种字符转换,而且还有杂项显示问题。
除此之外,一切似乎都运行良好,所以我把它扔掉了。如果您看到明显的错误,请发表评论。我欢迎任何改进它的建议。
除非我遗漏了什么,否则如果底层数据库具有 UUID 类型,上述解决方案将有效。如果没有,您可能会在创建表时遇到错误。我想出的解决方案最初是针对 MSSqlServer,然后最终使用了 MySql,所以我认为我的解决方案更灵活一些,因为它似乎在 mysql 和 sqlite 上运行良好。还没有打扰检查postgres。
【讨论】:
是的,我是在看到 Jacob 的回答推荐后发布的。 请注意,如果您使用的是 0.6 或更高版本,Tom 的解决方案中的 MSBinary 导入语句应更改为“from sqlalchemy.dialects.mysql.base import MSBinary”。来源:mail-archive.com/sqlalchemy@googlegroups.com/msg18397.html “我写了这个”是一个死链接。 另见the UUIDType that ships with SQLAlchemy-utils @codeninja postgresql 已经有原生 UUID 类型,所以直接使用sqlalchemy.dialects.postgresql.UUID
即可。见Backend-agnostic GUID Type【参考方案3】:
如果您对具有 UUID 值的“字符串”列感到满意,这里有一个简单的解决方案:
def generate_uuid():
return str(uuid.uuid4())
class MyTable(Base):
__tablename__ = 'my_table'
uuid = Column(String, name="uuid", primary_key=True, default=generate_uuid)
【讨论】:
不要将 UUID 存储为字符串,除非您使用的是不支持它们的非常奇怪的数据库。否则,可能会将所有数据存储为字符串... ;) @Nick 为什么?有什么缺点? @rayepps - 有很多缺点 - 有一些是最重要的:大小 - 字符串 uuid 占用空间的两倍 - 16 字节与 32 个字符 - 不包括任何格式化程序。处理时间 - 随着数据集变大,更多字节 = CPU 的更多处理时间。 uuid 字符串格式因语言而异,需要添加额外的翻译。有人更容易滥用该列,因为您可以在其中放置任何不是 uuid 的东西。这应该足够开始了。 出于性能问题,您不应将字符串用作 uuid 的列。更推荐使用 Binary(16)。 sqlite 是否有资格成为“非常奇怪的数据库”? :P【参考方案4】:我使用了SQLAlchemy-Utils
package 中的UUIDType
。
【讨论】:
我目前正在尝试使用这个,问题是我得到一个错误:raise InvalidStatus("notfound: k. (cls=cls)".format(k=k, cls=cls))
alchemyjsonschema.InvalidStatus: notfound: BINARY(16). (cls=<class 'sqlalchemy_utils.types.uuid.UUIDType'>)
你们收到错误了吗:NameError: name 'sqlalchemy_utils' is not defined
?
SQLAlchemy-Utils
是第三方包,需要先安装:pip install sqlalchemy-utils
这是要走的路,尽管您的迁移需要帐户或系统具有 UUID 与 CHAR/BINARY 值的 uuid。【参考方案5】:
由于您使用的是 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)
【讨论】:
这应该是那些使用 PostgreSQL 数据库的开发者唯一接受的答案。【参考方案6】:这是一种基于 SQLAlchemy 文档中的 Backend agnostic 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)
【讨论】:
这个有什么用?【参考方案7】:如果有人感兴趣,我一直在使用 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
【讨论】:
【参考方案8】:我们可以使用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))
更多详情我们可以参考official documentation。
【讨论】:
【参考方案9】:您可以尝试写一个custom type,例如:
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's answer,还有this blog entry。它看起来很相似,只是它是types.TypeDecorator
的子类,而不是types.TypeEngine
。哪一种方法比另一种方法有优势或劣势?
这甚至不起作用,它只是文档中虚拟类型示例的剪切和粘贴工作。下面汤姆威利斯的回答要好得多。
不需要default=?
吗?例如Column('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)
链接指向“页面未找到”,docs.sqlalchemy.org/en/13/core/… 可能与旧链接很接近以上是关于如何在 SQLAlchemy 中使用 UUID?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 SQLAlchemy 将列默认设置为 PostgreSQL 函数?
如何使用 sqlalchemy_utils.dependent_objects()?
SQLAlchemy PostgreSQL row_to_json关系