Apakah ada cara untuk mendefinisikan kolom (kunci utama) sebagai UUID di SQLAlchemy jika menggunakan PostgreSQL (Postgres)?
Apakah ada cara untuk mendefinisikan kolom (kunci utama) sebagai UUID di SQLAlchemy jika menggunakan PostgreSQL (Postgres)?
Jawaban:
Dialek sqlalchemy postgres mendukung kolom UUID. Ini mudah (dan pertanyaannya secara khusus adalah postgres) - Saya tidak mengerti mengapa jawaban lainnya begitu rumit.
Berikut ini contohnya:
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)
Berhati-hatilah untuk tidak melewatkan memasukkan callable
uuid.uuid4
ke dalam definisi kolom, daripada memanggil fungsi itu sendiri dengan uuid.uuid4()
. Jika tidak, Anda akan memiliki nilai skalar yang sama untuk semua instance kelas ini. Lebih detail di sini :
Ekspresi skalar, Python callable, atau ColumnElement yang mewakili nilai default untuk kolom ini, yang akan dipanggil saat penyisipan jika kolom ini tidak ditentukan dalam klausa VALUES penyisipan.
uuid.uuid4
).
Column(UUID(as_uuid=True) ...)
Column
dan Integer
diimpor di bagian atas cuplikan kode, atau diubah menjadi membaca db.Column
dandb.Integer
Saya menulis ini dan domainnya hilang tetapi inilah nyali ....
Terlepas dari bagaimana perasaan kolega saya yang benar-benar peduli dengan desain database yang tepat tentang UUID dan GUID yang digunakan untuk bidang kunci. Saya sering merasa perlu melakukannya. Saya pikir ini memiliki beberapa keunggulan dibandingkan autoincrement yang membuatnya sepadan.
Saya telah menyempurnakan jenis kolom UUID selama beberapa bulan terakhir dan saya pikir saya akhirnya berhasil.
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)))
Saya percaya menyimpan sebagai biner (16 byte) seharusnya menjadi lebih efisien daripada representasi string (36 byte?), Dan tampaknya ada beberapa indikasi bahwa pengindeksan blok 16 byte harus lebih efisien di mysql daripada string. Saya tidak berharap itu menjadi lebih buruk.
Satu kelemahan yang saya temukan adalah bahwa setidaknya di phpymyadmin, Anda tidak dapat mengedit catatan karena secara implisit mencoba melakukan semacam konversi karakter untuk "pilih * dari tabel di mana id = ..." dan ada masalah tampilan lain-lain.
Selain itu semuanya tampaknya berfungsi dengan baik, jadi saya membuangnya ke sana. Tinggalkan komentar jika Anda melihat kesalahan yang mencolok. Saya menerima saran untuk memperbaikinya.
Kecuali saya melewatkan sesuatu, solusi di atas akan berfungsi jika database yang mendasarinya memiliki jenis UUID. Jika tidak, Anda mungkin akan mendapatkan kesalahan saat tabel dibuat. Solusi yang saya buat saya awalnya menargetkan MSSqlServer dan kemudian menggunakan MySql pada akhirnya, jadi saya pikir solusi saya sedikit lebih fleksibel karena tampaknya berfungsi dengan baik pada mysql dan sqlite. Belum repot-repot memeriksa postgres.
sqlalchemy.dialects.postgresql.UUID
secara langsung. lihat Jenis GUID backend-agnostik
Jika Anda senang dengan kolom 'String' yang memiliki nilai UUID, berikut ini solusi sederhana:
def generate_uuid():
return str(uuid.uuid4())
class MyTable(Base):
__tablename__ = 'my_table'
uuid = Column(String, name="uuid", primary_key=True, default=generate_uuid)
Saya telah menggunakan UUIDType
dari SQLAlchemy-Utils
paket: 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
adalah paket pihak ketiga, Anda harus menginstalnya terlebih dahulu:pip install sqlalchemy-utils
Karena Anda menggunakan Postgres, ini seharusnya berfungsi:
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)
Berikut adalah pendekatan yang didasarkan pada GUID agnostik Backend dari dokumen SQLAlchemy, tetapi menggunakan bidang BINARY untuk menyimpan UUID dalam database non-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)
Jika ada yang tertarik, saya telah menggunakan jawaban Tom Willis, tetapi bermanfaat untuk menambahkan string ke konversi uuid.UUID dalam metode 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
Anda dapat mencoba menulis tipe khusus , misalnya:
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
bukan types.TypeEngine
. Apakah salah satu pendekatan memiliki keuntungan atau kerugian dibandingkan yang lain?
default=?
? misalnyaColumn('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)