Як я можу використовувати UUID в SQLAlchemy?


94

Чи є спосіб визначити стовпець (первинний ключ) як UUID в SQLAlchemy, якщо використовується PostgreSQL (Postgres)?


2
На жаль, тип Baidkend-агностичного GUID із документації SQLAlchemy для типів стовпців, схоже, не працює для первинних ключів у механізмах баз даних SQLite. Не зовсім такий екуменічний, як я сподівався.
adamek

SQLAlchemy utils надає декоратор UUIDType , немає необхідності винаходити колесо заново
Філіпе Безерра де Соуза

Відповіді:


153

Діалект postlares 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 вставки.


6
Я повністю з вами згоден. Деякі інші відповіді є крутими для інших баз даних, але для postgres це найчистіше рішення. (Ви також можете встановити за замовчуванням як uuid.uuid4).
pacha

1
Чи можете ви надати MWE? А може, серіалізатор у flask_sqlalchemy розуміє тип UUID? Код у pastebin нижче помилок, pastebin.com/hW8KPuYw
Brandon Dube

1
нічого, якщо ви хочете використовувати UUID-об'єкти зі stdlib, зробітьColumn(UUID(as_uuid=True) ...)
Brandon Dube

1
Дякую! Це може бути добре , якби Columnі Integerбули завезені в верхній частині фрагмента коду, або були змінені , щоб читати db.Columnіdb.Integer
Грег Sadetsky

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безпосередньо. див. тип
агіднічного

24

Якщо вас влаштовує стовпець "Рядок", що має значення 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 для проблем із продуктивністю. Більш рекомендований бінарний файл (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
Lopac

Це такий шлях, хоча ваші міграції потребують облікового запису або систем, які мають значення UUID проти CHAR / BINARY для uuids.
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

Ось підхід, заснований на агностичному GUID Backend з документів 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)

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. Чи має підхід перевагу чи недолік перед іншим?
Jacob Gabrielson

11
Це навіть не працює, це просто вирізати-вставити роботу з прикладу фіктивного типу з документації. Відповідь Тома Вілліса набагато краща.
Джессі Дійон

Хіба це не потрібно default=?? наприкладColumn('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)
iJames

Посилання вказує на "Сторінку не знайдено", docs.sqlalchemy.org/en/13/core/… , близька до старої
barbsan
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.