Как я могу использовать UUID в SQLAlchemy?

94

Есть ли способ определить столбец (первичный ключ) как UUID в SQLAlchemy при использовании PostgreSQL (Postgres)?

Василь
источник
2
К сожалению, не зависящий от серверной части тип GUID из документации SQLAlchemy для типов столбцов, похоже, не работает для первичных ключей в механизмах баз данных SQLite. Не настолько вселенский, как я надеялся.
adamek
Утилиты SQLAlchemy предоставляют декоратор UUIDType , нет необходимости изобретать велосипед
Филипе Безерра де Соуза,

Ответы:

152

Диалект postgres sqlalchemy поддерживает столбцы 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 вставки.

JDiMatteo
источник
6
Я полностью согласен с тобой. Некоторые другие ответы хороши для других баз данных, но для postgres это самое чистое решение. (Вы также можете установить значение по умолчанию как uuid.uuid4).
pacha
1
Можете ли вы предоставить MWE? Или, может быть, сериализатор в flask_sqlalchemy понимает тип UUID? Код ошибки в pastebin ниже, pastebin.com/hW8KPuYw
Brandon Dube
1
неважно, если вы хотите использовать объекты UUID из stdlib, сделайтеColumn(UUID(as_uuid=True) ...)
Брэндон Дьюб
1
Спасибо! Было бы неплохо, если бы Columnи Integerбыли импортированы в верхней части фрагмента кода, или были изменены на чтение db.Columnиdb.Integer
Грег Садецкий
1
Нет необходимости в @nephanth
Филипе Безерра де Соуза
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 байтов?). И, похоже, есть некоторые признаки того, что индексирование 16-байтовых блоков должно быть более эффективным в mysql, чем строки. В любом случае я бы не ожидал, что будет хуже.

Один недостаток, который я обнаружил, заключается в том, что, по крайней мере, в phpymyadmin вы не можете редактировать записи, потому что он неявно пытается выполнить какое-то преобразование символов для «select * from table where id = ...» и есть разные проблемы с отображением.

В остальном вроде все работает нормально, так что бросаю. Оставьте комментарий, если увидите в нем явную ошибку. Я приветствую любые предложения по его улучшению.

Если мне что-то не хватает, вышеуказанное решение будет работать, если базовая база данных имеет тип UUID. В противном случае вы, вероятно, получите ошибки при создании таблицы. Решение, которое я придумал, я изначально нацеливался на MSSqlServer, а затем в конце перешел на MySql, поэтому я думаю, что мое решение немного более гибкое, поскольку, похоже, оно отлично работает с mysql и sqlite. Еще не удосужился проверить postgres.

Том Уиллис
источник
да, я разместил это после того, как увидел рекомендации из ответа Джейкоба.
Том Уиллис
4
Обратите внимание, что если вы используете версию 0.6 или выше, оператор импорта MSBinary в решении Тома следует изменить на «from sqlalchemy.dialects.mysql.base import MSBinary». Источник: mail-archive.com/sqlalchemy@googlegroups.com/msg18397.html
Кэл Джейкобсон,
2
"Я написал это" - мертвая ссылка.
julx
2
@codeninja postgresql уже имеет собственный тип UUID, поэтому просто используйте его sqlalchemy.dialects.postgresql.UUIDнапрямую. см. Backend-
agnostic
24

Если вас устраивает столбец String со значением 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)
Кушал Ахмед
источник
5
Не храните UUID в виде строки, если вы не используете действительно странную базу данных, которая их не поддерживает. в противном случае, возможно, сохраните все ваши данные в виде строк ...;)
Ник
@ Ник, почему? какой недостаток?
rayepps
6
@rayepps - есть много недостатков - некоторые из них неуместны: размер - строка uuid занимает в два раза больше места - 16 байтов против 32 символов - не включая никаких форматеров. Время обработки - больше байтов = больше времени обработки ЦП по мере увеличения набора данных. Строковые форматы uuid различаются в зависимости от языка, поэтому требуются дополнительные переводы. Легче кому-то неправильно использовать столбец, так как вы можете поместить туда все, что не является uuid. Этого должно быть достаточно для начала.
Ник
Вы не должны использовать строки в качестве столбцов для uuid из-за проблем с производительностью. Более рекомендуется двоичный (16).
Кирилл Н.
19

Я использовал UUIDTypeиз SQLAlchemy-Utilsпакета: http://sqlalchemy-utils.readthedocs.org/en/latest/data_types.html#module-sqlalchemy_utils.types.uuid

Берислав Лопац
источник
В настоящее время я пытаюсь использовать это, проблема в том, что я получаю сообщение об ошибке: 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
Берислав Лопак
Это правильный путь, хотя для ваших миграций требуется учетная запись или системы, которые имеют значения UUID и CHAR / BINARY для uuid.
rjurney
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.
Хосе Л. Патиньо,
5

Вот подход, основанный на независимом от Backend GUID из документации SQLAlchemy, но с использованием поля 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)
Zwirbeltier
источник
1
Как бы это было использовать?
CodeTrooper,
3

В случае, если кому-то интересно, я использовал ответ Тома Уиллиса, но нашел полезным добавить строку в преобразование uuid.UUID в методе process_bind_param

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),
)
Флориан Бёш
источник
В дополнение к ответу Флориана есть также эта запись в блоге . Он выглядит похоже, за исключением того, что он создает подклассы types.TypeDecoratorвместо types.TypeEngine. Есть ли у одного из подходов преимущество или недостаток перед другим?
Джейкоб Гэбриэлсон
11
Это даже не работает, это просто работа по вырезанию и вставке из примера фиктивного типа из документации. Ответ Тома Уиллиса ниже намного лучше.
Джесси Диллон
Разве это не нужно default=?? напримерColumn('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)
iJames
Ссылка указывает на «Страница не найдена», docs.sqlalchemy.org/en/13/core/… вероятно, близка к старой
barbsan