如何在 SQLAlchemy 中使用 UUID?

如果使用 PostgreSQL(Postgres) ,有没有办法在 SQLAlchemy中将列(主键)定义为 UUID

129249 次浏览

例如,你可以尝试写一个 自定义类型:

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

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

不管真正关心正确的数据库设计的同事如何看待用于关键字段的 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 上工作得很好。还没来得及查邮件呢。

如果有人感兴趣,我一直在使用 Tom Willis answer,但发现在 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

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

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)

我用的是 SQLAlchemy-Utils包裹UUIDType

如果您对具有 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)

方言支持 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()调用函数本身。否则,这个类的所有实例的标量值都是相同的。更多细节 给你:

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

既然你在使用 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)

我们可以用 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)

更多细节我们可以参考 正式文件

我遇到了同样的问题,这应该工作,它为我工作:

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

如果您使用 PostgreSQL < 14,我认为您需要添加这个扩展包:

CREATE EXTENSION IF NOT EXISTS "pgcrypto";

你也可以使用 uuid_generate_v4(),你需要添加扩展包:

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";