Gibt es eine Möglichkeit, eine Spalte (Primärschlüssel) als UUID in SQLAlchemy zu definieren, wenn PostgreSQL (Postgres) verwendet wird?
Gibt es eine Möglichkeit, eine Spalte (Primärschlüssel) als UUID in SQLAlchemy zu definieren, wenn PostgreSQL (Postgres) verwendet wird?
Antworten:
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.
uuid.uuid4
.)
Column(UUID(as_uuid=True) ...)
Column
und Integer
oben im Code-Snippet importiert wurden oder geändert wurden, um zu lesen db.Column
unddb.Integer
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.
sqlalchemy.dialects.postgresql.UUID
direkt. siehe Backend-agnostischer GUID-Typ
Siehe auch das Rezept für den Backend-agnostischen GUID-Typ in der SQLAlchemy-Dokumentation für Spaltentypen.
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)
Ich habe das UUIDType
aus dem SQLAlchemy-Utils
Paket verwendet: 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'>)
NameError: name 'sqlalchemy_utils' is not defined
?
SQLAlchemy-Utils
ist ein Paket von Drittanbietern, müssen Sie es zuerst installieren:pip install sqlalchemy-utils
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)
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)
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
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),
)
types.TypeDecorator
anstelle von types.TypeEngine
. Hat einer der beiden Ansätze einen Vor- oder Nachteil gegenüber dem anderen?
default=?
? zBColumn('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)