Jak mogę używać identyfikatorów UUID w SQLAlchemy?

96

Czy istnieje sposób na zdefiniowanie kolumny (klucza podstawowego) jako UUID w SQLAlchemy, jeśli używasz PostgreSQL (Postgres)?

Wasilij
źródło
2
Niestety typ GUID niezależny od zaplecza z dokumentacji SQLAlchemy dla typów kolumn nie wydaje się działać dla kluczy podstawowych w silnikach baz danych SQLite. Nie tak ekumeniczne, na jakie liczyłem.
adamek
SQLAlchemy utils zapewnia dekorator UUIDType , nie ma potrzeby odkrywania na nowo koła
Filipe Bezerra de Sousa

Odpowiedzi:

160

Dialekt sqlalchemy postgres obsługuje kolumny UUID. Jest to łatwe (a pytanie jest szczególnie postgres) - nie rozumiem, dlaczego wszystkie inne odpowiedzi są tak skomplikowane.

Oto przykład:

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, unique=True)

Uważaj, aby nie przegapić przekazania callable uuid.uuid4do definicji kolumny, zamiast wywoływania samej funkcji za pomocą uuid.uuid4(). W przeciwnym razie będziesz mieć tę samą wartość skalarną dla wszystkich wystąpień tej klasy. Więcej szczegółów tutaj :

Wyrażenie skalarne, wywoływalne w Pythonie lub wyrażenie ColumnElement reprezentujące wartość domyślną dla tej kolumny, która zostanie wywołana po wstawieniu, jeśli ta kolumna nie jest inaczej określona w klauzuli VALUES elementu insert.

JDiMatteo
źródło
6
Całkowicie się z tobą zgadzam. Niektóre inne odpowiedzi są fajne dla innych baz danych, ale dla postgres jest to najczystsze rozwiązanie. (Możesz również ustawić wartość domyślną jako uuid.uuid4).
pacha
1
Czy możesz zapewnić MWE? A może serializator w flask_sqlalchemy rozumie typ UUID? Kod w pastebin poniżej błędy, pastebin.com/hW8KPuYw
Brandon Dube
1
nieważne, jeśli chcesz używać obiektów UUID ze standardowego biblioteki, zróbColumn(UUID(as_uuid=True) ...)
Brandon Dube
1
Dziękuję Ci! Byłoby miło, gdyby Columni Integerzostały zaimportowane na górze fragmentu kodu lub zostały zmienione na odczyt db.Columnidb.Integer
Greg Sadetsky
2
Nie, nie ma potrzeby @nephanth
Filipe Bezerra de Sousa
65

Napisałem to i domena zniknęła, ale oto odwaga ...

Niezależnie od tego, jak moi koledzy, którym naprawdę zależy na odpowiednim zaprojektowaniu bazy danych, oceniają UUID i GUID używane w kluczowych polach. Często stwierdzam, że muszę to zrobić. Myślę, że ma pewne zalety w stosunku do autoinkrementacji, które sprawiają, że warto.

Przez ostatnie kilka miesięcy udoskonalałem typ kolumny UUID i myślę, że w końcu mam to solidne.

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

Uważam, że przechowywanie jako binarne (16 bajtów) powinno być bardziej wydajne niż reprezentacja w postaci ciągu (36 bajtów?). Wydaje się, że jest pewne wskazanie, że indeksowanie 16-bajtowych bloków powinno być bardziej wydajne w mysql niż w łańcuchach. I tak nie spodziewałbym się, że będzie gorzej.

Jedną z wad, które znalazłem, jest to, że przynajmniej w phpymyadmin nie możesz edytować rekordów, ponieważ niejawnie próbuje on wykonać jakąś konwersję znaków dla "wybierz * z tabeli, gdzie id = ..." i jest wiele problemów z wyświetlaniem.

Poza tym wszystko wydaje się działać dobrze, więc wyrzucam to tam. Zostaw komentarz, jeśli zobaczysz rażący błąd. Z zadowoleniem przyjmuję wszelkie sugestie dotyczące jego ulepszenia.

O ile czegoś nie brakuje, powyższe rozwiązanie będzie działać, jeśli bazowa baza danych ma typ UUID. Jeśli tak się nie stanie, prawdopodobnie wystąpią błędy podczas tworzenia tabeli. Rozwiązanie, które wymyśliłem, początkowo celowałem w MSSqlServer, a następnie przeszedłem na MySql, więc myślę, że moje rozwiązanie jest trochę bardziej elastyczne, ponieważ wydaje się działać dobrze na mysql i sqlite. Nie zawracałem sobie głowy sprawdzaniem postgresów.

Tom Willis
źródło
tak, opublikowałem to po tym, jak zobaczyłem skierowania z odpowiedzi Jacoba.
Tom Willis,
4
Zauważ, że jeśli używasz wersji 0.6 lub nowszej, instrukcja importu MSBinary w rozwiązaniu Toma powinna zostać zmieniona na „from sqlalchemy.dialects.mysql.base import MSBinary”. Źródło: mail-archive.com/[email protected]/msg18397.html
Cal Jacobson,
2
„Napisałem to” to martwy link.
julx
2
@codeninja postgresql ma już natywny typ UUID, więc po prostu użyj go sqlalchemy.dialects.postgresql.UUIDbezpośrednio. patrz Backend-agnostic GUID Type
cowbert
24

Jeśli jesteś zadowolony z kolumny „Ciąg” z wartością UUID, oto proste rozwiązanie:

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

class MyTable(Base):
    __tablename__ = 'my_table'

    uuid = Column(String, name="uuid", primary_key=True, default=generate_uuid)
Kushal Ahmed
źródło
5
Nie przechowuj identyfikatora UUID jako ciągu znaków, chyba że używasz naprawdę dziwnej bazy danych, która ich nie obsługuje. w przeciwnym razie może przechowywać wszystkie swoje dane jako ciągi znaków ...;)
Nick
@Nick why? jaki jest minus?
rayepps
6
@rayepps - jest wiele wad - kilka z góry na myśli: rozmiar - ciąg uuid zajmuje dwa razy więcej miejsca - 16 bajtów w porównaniu z 32 znakami - bez żadnych elementów formatujących. Czas przetwarzania - więcej bajtów = dłuższy czas przetwarzania przez procesor w miarę powiększania się zbioru danych. Formaty ciągów uuid różnią się w zależności od języka, dodając dodatkowe wymagane tłumaczenia. Komuś łatwiej będzie nadużywać kolumny, ponieważ można w niej umieścić wszystko, co nie jest uuidem. To powinno wystarczyć na początek.
Nick
Nie należy używać ciągów znaków jako kolumn dla identyfikatora użytkownika ze względu na problemy z wydajnością. Bardziej zalecany jest plik binarny (16).
Cyril N.
19

Użyłem UUIDTypez SQLAlchemy-Utilspakietu: http://sqlalchemy-utils.readthedocs.org/en/latest/data_types.html#module-sqlalchemy_utils.types.uuid

Berislav Lopac
źródło
Obecnie próbuję tego użyć, problem polega na tym, że pojawia się błąd: raise InvalidStatus("notfound: {k}. (cls={cls})".format(k=k, cls=cls)) alchemyjsonschema.InvalidStatus: notfound: BINARY(16). (cls=<class 'sqlalchemy_utils.types.uuid.UUIDType'>)
CodeTrooper
Czy otrzymaliście błąd NameError: name 'sqlalchemy_utils' is not defined:?
Walter,
1
SQLAlchemy-Utilsjest pakietem innej firmy, musisz go najpierw zainstalować:pip install sqlalchemy-utils
Berislav Lopac
To jest droga do zrobienia, chociaż Twoje migracje muszą uwzględniać lub systemy, które mają wartości UUID vs CHAR / BINARY dla uuidów.
rjurney
9

Ponieważ używasz Postgres, powinno to działać:

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)
Granat
źródło
1
To powinna być jedyna akceptowana odpowiedź dla tych programistów, którzy używają bazy danych PostgreSQL.
José L. Patiño
6

Oto podejście oparte na identyfikatorze GUID agnostycznym zaplecza z dokumentacji SQLAlchemy, ale przy użyciu pola BINARY do przechowywania identyfikatorów UUID w bazach danych innych niż 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
źródło
1
Jaki byłby z tego pożytek?
CodeTrooper
3

Na wypadek, gdyby ktoś był zainteresowany, korzystałem z odpowiedzi Toma Willisa, ale okazało się, że warto dodać ciąg do uuid. Konwersja UUID w metodzie 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
Nemeth
źródło
-19

Możesz spróbować napisać niestandardowy typ , na przykład:

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 Bösch
źródło
Oprócz odpowiedzi Floriana jest też ten wpis na blogu . Wygląda podobnie, z tym wyjątkiem, że types.TypeDecoratorzamiast klasy zawiera podklasy types.TypeEngine. Czy któreś podejście ma przewagę lub wady w stosunku do drugiego?
Jacob Gabrielson
11
To nawet nie działa, to tylko zadanie wycinania i wklejania z przykładowego typu fikcyjnego z dokumentacji. Odpowiedź Toma Willisa poniżej jest znacznie lepsza.
Jesse Dhillon
Czy to nie jest potrzebne default=?? np.Column('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)
iJames
Odsyłacze do „Nie znaleziono strony”, docs.sqlalchemy.org/en/13/core/… są prawdopodobnie zbliżone do starego
barbsan