Apakah ada cara untuk mendefinisikan kolom (kunci utama) sebagai UUID di SQLAlchemy jika menggunakan PostgreSQL (Postgres)?
python
postgresql
orm
sqlalchemy
uuid
Vasil
sumber
sumber
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 denganuuid.uuid4()
. Jika tidak, Anda akan memiliki nilai skalar yang sama untuk semua instance kelas ini. Lebih detail di sini :sumber
uuid.uuid4
).Column(UUID(as_uuid=True) ...)
Column
danInteger
diimpor di bagian atas cuplikan kode, atau diubah menjadi membacadb.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.
sumber
sqlalchemy.dialects.postgresql.UUID
secara langsung. lihat Jenis GUID backend-agnostikJika 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)
sumber
Saya telah menggunakan
UUIDType
dariSQLAlchemy-Utils
paket: http://sqlalchemy-utils.readthedocs.org/en/latest/data_types.html#module-sqlalchemy_utils.types.uuidsumber
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)
sumber
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)
sumber
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
sumber
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), )
sumber
types.TypeDecorator
bukantypes.TypeEngine
. Apakah salah satu pendekatan memiliki keuntungan atau kerugian dibandingkan yang lain?default=?
? misalnyaColumn('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)