Wie kann ich UUIDs in SQLAlchemy verwenden?

90

Gibt es eine Möglichkeit, eine Spalte (Primärschlüssel) als UUID in SQLAlchemy zu definieren, wenn PostgreSQL (Postgres) verwendet wird?

Vasil
quelle
2
Leider scheint der Backend-agnostische GUID-Typ aus der SQLAlchemy-Dokumentation für Spaltentypen für Primärschlüssel in SQLite-Datenbankmodulen nicht zu funktionieren. Nicht ganz so ökumenisch, wie ich es mir erhofft hatte.
Adamek
SQLAlchemy utils bietet UUIDType-Dekorateur , das Rad muss nicht neu erfunden werden
Filipe Bezerra de Sousa

Antworten:

146

Der Postgres-Dialekt von sqlalchemy unterstützt UUID-Spalten. Das ist einfach (und die Frage ist speziell postgres) - ich verstehe nicht, warum die anderen Antworten alle so kompliziert sind.

Hier ist ein Beispiel:

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)

Achten Sie darauf, dass Sie die Übergabe an die Spaltendefinition nicht verpassen callable uuid.uuid4, anstatt die Funktion selbst mit aufzurufen uuid.uuid4(). Andernfalls haben Sie für alle Instanzen dieser Klasse den gleichen Skalarwert. Weitere Details hier :

Ein skalarer, aufrufbarer Python- oder ColumnElement-Ausdruck, der den Standardwert für diese Spalte darstellt und beim Einfügen aufgerufen wird, wenn diese Spalte in der VALUES-Klausel des Einfügens nicht anders angegeben ist.

JDiMatteo
quelle
6
Ich stimme dir vollkommen zu. Einige der anderen Antworten sind cool für andere Datenbanken, aber für Postgres ist dies die sauberste Lösung. (Sie können auch einen Standardwert als festlegen uuid.uuid4.)
Pacha
1
Können Sie eine MWE bereitstellen? Oder versteht der Serializer in flask_sqlalchemy den UUID-Typ? Der Code im Pastebin unter Fehlern, pastebin.com/hW8KPuYw
Brandon Dube
1
egal, wenn Sie UUID-Objekte von stdlib verwenden möchten, tun SieColumn(UUID(as_uuid=True) ...)
Brandon Dube
1
Danke dir! Es könnte schön sein, wenn Columnund Integeroben im Code-Snippet importiert wurden oder geändert wurden, um zu lesen db.Columnunddb.Integer
Greg Sadetsky
1
Nein, es gibt keine Notwendigkeit @nephanth
Filipe Bezerra de Sousa
64

Ich habe das geschrieben und die Domain ist weg, aber hier ist der Mut ...

Unabhängig davon, wie meine Kollegen, die sich wirklich für das richtige Datenbankdesign interessieren, UUIDs und GUIDs, die für Schlüsselfelder verwendet werden, beurteilen. Ich finde oft, dass ich es tun muss. Ich denke, es hat einige Vorteile gegenüber der automatischen Erhöhung, die es wert machen.

Ich habe in den letzten Monaten einen UUID-Spaltentyp verfeinert und ich denke, ich habe ihn endlich solide.

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

Ich bin der Meinung, dass das Speichern als Binärdatei (16 Byte) effizienter sein sollte als die Zeichenfolgendarstellung (36 Byte?). Und es scheint Anzeichen dafür zu geben, dass die Indizierung von 16-Byte-Blöcken in MySQL effizienter sein sollte als Zeichenfolgen. Ich würde sowieso nicht erwarten, dass es schlimmer wird.

Ein Nachteil, den ich festgestellt habe, ist, dass Sie zumindest in phpymyadmin keine Datensätze bearbeiten können, da implizit versucht wird, eine Zeichenkonvertierung für "select * from table where id = ..." durchzuführen, und es gibt verschiedene Anzeigeprobleme.

Davon abgesehen scheint alles gut zu funktionieren, und so werfe ich es da raus. Hinterlasse einen Kommentar, wenn du einen krassen Fehler siehst. Ich freue mich über Verbesserungsvorschläge.

Sofern mir nichts fehlt, funktioniert die obige Lösung, wenn die zugrunde liegende Datenbank einen UUID-Typ hat. Wenn dies nicht der Fall ist, werden beim Erstellen der Tabelle wahrscheinlich Fehler angezeigt. Die Lösung, die ich mir ausgedacht habe, war ursprünglich das Ziel von MSSqlServer und dann MySql. Daher denke ich, dass meine Lösung etwas flexibler ist, da sie auf MySQL und SQLite gut zu funktionieren scheint. Ich habe mich noch nicht darum gekümmert, Postgres zu überprüfen.

Tom Willis
quelle
Ja, ich habe es gepostet, nachdem ich Empfehlungen von Jacobs Antwort gesehen habe.
Tom Willis
4
Beachten Sie, dass bei Verwendung von Version 0.6 oder höher die MSBinary-Importanweisung in Toms Lösung in "from sqlalchemy.dialects.mysql.base import MSBinary" geändert werden sollte. Quelle: mail-archive.com/[email protected]/msg18397.html
Cal Jacobson
2
"Ich habe das geschrieben" ist eine tote Verbindung.
Julx
2
Siehe auch den UUIDType, der mit SQLAlchemy-utils
geliefert wird
2
@codeninja postgresql hat bereits einen nativen UUID-Typ, verwenden Sie ihn also sqlalchemy.dialects.postgresql.UUIDdirekt. siehe Backend-agnostischer GUID-Typ
Cowbert
25

Wenn Sie mit einer 'String'-Spalte mit UUID-Wert zufrieden sind, finden Sie hier eine einfache Lösung:

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
quelle
4
Speichern Sie die UUID nicht als Zeichenfolge, es sei denn, Sie verwenden eine wirklich seltsame Datenbank, die sie nicht unterstützt. Andernfalls speichern Sie möglicherweise alle Ihre Daten als Zeichenfolgen ...;)
Nick
@ Nick warum? Was ist der Nachteil?
Rayepps
4
@rayepps - es gibt viele Nachteile - ein paar davon: Größe - String-UUID nimmt doppelt so viel Platz ein - 16 Byte gegenüber 32 Zeichen - ohne Formatierer. Verarbeitungszeit - mehr Bytes = mehr Verarbeitungszeit durch die CPU, wenn Ihr Datensatz größer wird. UUID-Zeichenfolgenformate unterscheiden sich je nach Sprache und fügen zusätzliche erforderliche Übersetzungen hinzu. Es ist für jemanden einfacher, die Spalte zu missbrauchen, da Sie alles hineinstecken können, Dinge, die keine UUIDs sind. Das sollte reichen, um zu beginnen.
Nick
19

Ich habe das UUIDTypeaus dem SQLAlchemy-UtilsPaket verwendet: http://sqlalchemy-utils.readthedocs.org/en/latest/data_types.html#module-sqlalchemy_utils.types.uuid

Berislav Lopac
quelle
Ich versuche gerade, dies zu verwenden. Das Problem ist, dass ich eine Fehlermeldung raise InvalidStatus("notfound: {k}. (cls={cls})".format(k=k, cls=cls)) alchemyjsonschema.InvalidStatus: notfound: BINARY(16). (cls=<class 'sqlalchemy_utils.types.uuid.UUIDType'>)
erhalte
Habt ihr den Fehler erhalten : NameError: name 'sqlalchemy_utils' is not defined?
Walter
1
SQLAlchemy-Utilsist ein Paket von Drittanbietern, müssen Sie es zuerst installieren:pip install sqlalchemy-utils
Berislav Lopac
Dies ist der richtige Weg, obwohl Ihre Migrationen Konten oder Systeme benötigen, die UUID- oder CHAR / BINARY-Werte für UUIDs haben.
rjurney
7

Da Sie Postgres verwenden, sollte dies funktionieren:

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
quelle
Dies sollte die einzige akzeptierte Antwort für Entwickler sein, die eine PostgreSQL-Datenbank verwenden.
José L. Patiño
4

Hier ist ein Ansatz, der auf der Backend-agnostischen GUID aus den SQLAlchemy-Dokumenten basiert , jedoch ein BINARY-Feld verwendet, um die UUIDs in Nicht-Postgresql-Datenbanken zu speichern.

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
quelle
1
Was wäre die Verwendung davon?
CodeTrooper
3

Falls jemand interessiert ist, habe ich die Antwort von Tom Willis verwendet, aber es hat sich als nützlich erwiesen, der Konvertierung von uuid.UUID in der Methode process_bind_param eine Zeichenfolge hinzuzufügen

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
quelle
-19

Sie könnten versuchen, einen benutzerdefinierten Typ zu schreiben , zum Beispiel:

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
quelle
Neben Florians Antwort gibt es auch diesen Blogeintrag . Es sieht ähnlich aus, außer dass es Unterklassen types.TypeDecoratoranstelle von types.TypeEngine. Hat einer der beiden Ansätze einen Vor- oder Nachteil gegenüber dem anderen?
Jacob Gabrielson
11
Dies funktioniert nicht einmal, es ist nur ein Ausschneiden und Einfügen aus dem Dummy-Typ-Beispiel aus den Dokumenten. Die Antwort von Tom Willis unten ist viel besser.
Jesse Dhillon
Braucht es nicht ein default=?? zBColumn('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)
iJames
Link verweist auf "Seite nicht gefunden", docs.sqlalchemy.org/en/13/core/… ist wahrscheinlich in der Nähe des alten
Barbansan