Czy istnieje sposób na zdefiniowanie kolumny (klucza podstawowego) jako UUID w SQLAlchemy, jeśli używasz PostgreSQL (Postgres)?
python
postgresql
orm
sqlalchemy
uuid
Wasilij
źródło
źródło
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 :źródło
uuid.uuid4
).Column(UUID(as_uuid=True) ...)
Column
iInteger
zostały zaimportowane na górze fragmentu kodu lub zostały zmienione na odczytdb.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.
źródło
sqlalchemy.dialects.postgresql.UUID
bezpośrednio. patrz Backend-agnostic GUID TypeJeś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)
źródło
Użyłem
UUIDType
zSQLAlchemy-Utils
pakietu: http://sqlalchemy-utils.readthedocs.org/en/latest/data_types.html#module-sqlalchemy_utils.types.uuidźródło
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)
źródło
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)
źródło
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
źródło
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), )
źródło
types.TypeDecorator
zamiast klasy zawiera podklasytypes.TypeEngine
. Czy któreś podejście ma przewagę lub wady w stosunku do drugiego?default=?
? np.Column('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)