Bagaimana saya bisa menggunakan UUID di SQLAlchemy?

94

Apakah ada cara untuk mendefinisikan kolom (kunci utama) sebagai UUID di SQLAlchemy jika menggunakan PostgreSQL (Postgres)?

Vasil
sumber
2
Sayangnya Jenis GUID Backend-agnostik dari dokumentasi SQLAlchemy untuk jenis kolom tampaknya tidak berfungsi untuk kunci utama di mesin database SQLite. Tidak se-ekumenis seperti yang saya harapkan.
adamek
Utilitas SQLAlchemy menyediakan dekorator UUIDType , tidak perlu menemukan kembali roda
Filipe Bezerra de Sousa

Jawaban:

153

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.uuid4ke 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.

JDiMatteo
sumber
6
Saya sangat setuju dengan anda. Beberapa jawaban lain keren untuk database lain, tetapi untuk postgres ini adalah solusi terbersih. (Anda juga dapat mengatur default sebagai uuid.uuid4).
pacha
1
Bisakah Anda memberikan MWE? Atau mungkin serializer di flask_sqlalchemy memahami tipe UUID? Kode di pastebin di bawah kesalahan, pastebin.com/hW8KPuYw
Brandon Dube
1
Tidak masalah, jika Anda ingin menggunakan objek UUID dari stdlib, lakukanColumn(UUID(as_uuid=True) ...)
Brandon Dube
1
Terima kasih! Mungkin menyenangkan jika Columndan Integerdiimpor di bagian atas cuplikan kode, atau diubah menjadi membaca db.Columndandb.Integer
Greg Sadetsky
1
Tidak, tidak perlu @n elephanth
Filipe Bezerra de Sousa
64

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.

Tom Willis
sumber
ya saya mempostingnya setelah saya melihat referensi dari jawaban Yakub.
Tom Willis
4
Perhatikan bahwa jika Anda menggunakan versi 0.6 atau yang lebih baru, pernyataan impor MSBinary di solusi Tom harus diubah menjadi "dari sqlalchemy.dialects.mysql.base import MSBinary". Sumber: mail-archive.com/[email protected]/msg18397.html
Cal Jacobson
2
"Saya menulis ini" adalah tautan mati.
julx
2
@codeninja postgresql sudah memiliki jenis UUID asli, jadi gunakan saja sqlalchemy.dialects.postgresql.UUIDsecara langsung. lihat Jenis GUID backend-agnostik
cowbert
24

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)
Kushal Ahmed
sumber
5
Jangan menyimpan UUID sebagai string kecuali Anda menggunakan database yang sangat aneh yang tidak mendukungnya. jika tidak, mungkin menyimpan semua data Anda sebagai string ...;)
Nick
@Nick kenapa? apa sisi negatifnya?
rayepps
6
@rayepps - ada banyak kerugian - beberapa di luar pemikiran: ukuran - string uuid membutuhkan ruang dua kali lipat - 16bytes vs 32 karakter - tidak termasuk pemformat apa pun. Waktu pemrosesan - lebih banyak byte = lebih banyak waktu pemrosesan oleh CPU karena kumpulan data Anda semakin besar. Format string uuid berbeda menurut bahasa, menambahkan terjemahan yang diperlukan. Lebih mudah bagi seseorang untuk menyalahgunakan kolom, karena Anda dapat meletakkan apa saja di sana, hal-hal yang bukan cairan. Itu sudah cukup untuk memulai.
Nick
Anda tidak boleh menggunakan String sebagai kolom untuk fluida, untuk masalah kinerja. Binary (16) lebih direkomendasikan.
Cyril N.
19

Saya telah menggunakan UUIDTypedari SQLAlchemy-Utilspaket: http://sqlalchemy-utils.readthedocs.org/en/latest/data_types.html#module-sqlalchemy_utils.types.uuid

Berislav Lopac
sumber
Saat ini saya mencoba menggunakan ini, masalahnya adalah saya mendapatkan kesalahan: raise InvalidStatus("notfound: {k}. (cls={cls})".format(k=k, cls=cls)) alchemyjsonschema.InvalidStatus: notfound: BINARY(16). (cls=<class 'sqlalchemy_utils.types.uuid.UUIDType'>)
CodeTrooper
Apakah kalian menerima kesalahan: NameError: name 'sqlalchemy_utils' is not defined?
Walter
1
SQLAlchemy-Utilsadalah paket pihak ketiga, Anda harus menginstalnya terlebih dahulu:pip install sqlalchemy-utils
Berislav Lopac
Ini adalah cara yang harus ditempuh, meskipun migrasi Anda perlu akun atau sistem yang memiliki nilai UUID vs CHAR / BINARY untuk uuids.
rjurney
9

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)
Granat
sumber
1
Ini harus menjadi satu-satunya jawaban yang diterima untuk para pengembang yang menggunakan database PostgreSQL.
José L. Patiño
5

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)
zwirbeltier
sumber
1
Apa gunanya ini?
CodeTrooper
3

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
Nemeth
sumber
-19

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),
)
Florian Bösch
sumber
Selain jawaban Florian , ada juga entri blog ini . Ini terlihat serupa kecuali bahwa itu subclass, types.TypeDecoratorbukan types.TypeEngine. Apakah salah satu pendekatan memiliki keuntungan atau kerugian dibandingkan yang lain?
Jacob Gabrielson
11
Ini bahkan tidak berhasil, ini hanya pekerjaan potong-dan-tempel dari contoh tipe dummy dari dokumen. Jawaban Tom Willis di bawah ini jauh lebih baik.
Jesse Dhillon
Bukankah itu membutuhkan default=?? misalnyaColumn('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)
iJames
Tautan mengarah ke "Halaman tidak ditemukan", docs.sqlalchemy.org/en/13/core/… mungkin dekat dengan yang lama
barbsan