Czy istnieje sposób na zdefiniowanie kolumny (klucza podstawowego) jako UUID w SQLAlchemy, jeśli używasz PostgreSQL (Postgres)?
Czy istnieje sposób na zdefiniowanie kolumny (klucza podstawowego) jako UUID w SQLAlchemy, jeśli używasz PostgreSQL (Postgres)?
Odpowiedzi:
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.uuid4
do 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.
uuid.uuid4
).
Column(UUID(as_uuid=True) ...)
Column
i Integer
zostały zaimportowane na górze fragmentu kodu lub zostały zmienione na odczyt db.Column
idb.Integer
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.
sqlalchemy.dialects.postgresql.UUID
bezpośrednio. patrz Backend-agnostic GUID Type
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)
Użyłem UUIDType
z SQLAlchemy-Utils
pakietu: 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
jest pakietem innej firmy, musisz go najpierw zainstalować:pip install sqlalchemy-utils
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)
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)
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
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),
)
types.TypeDecorator
zamiast klasy zawiera podklasy types.TypeEngine
. Czy któreś podejście ma przewagę lub wady w stosunku do drugiego?
default=?
? np.Column('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)