ฉันจะใช้ UUIDs ใน SQLAlchemy ได้อย่างไร


94

มีวิธีกำหนดคอลัมน์ (คีย์หลัก) เป็นUUIDในSQLAlchemy หรือไม่ถ้าใช้PostgreSQL (Postgres)


2
น่าเสียดายที่ประเภท GUID ของแบ็กเอนด์ที่ไม่เชื่อเรื่องพระเจ้าจากเอกสาร SQLAlchemy สำหรับประเภทคอลัมน์ดูเหมือนจะไม่ทำงานกับคีย์หลักในเอ็นจิ้นฐานข้อมูล SQLite ไม่ค่อยเป็นสากลเท่าที่ฉันหวังไว้
adamek

โปรแกรมอรรถประโยชน์ SQLAlchemy ให้มัณฑนากร UUIDTypeโดยไม่จำเป็นต้องสร้างวงล้อใหม่
Filipe Bezerra de Sousa

คำตอบ:


153

ภาษาของ sqlalchemy postgres รองรับคอลัมน์ UUID นี่เป็นเรื่องง่าย (และคำถามนี้เป็นคำถามเฉพาะ) - ฉันไม่เข้าใจว่าทำไมคำตอบอื่น ๆ ถึงซับซ้อนขนาดนี้

นี่คือตัวอย่าง:

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)

ระวังจะไม่พลาดที่ผ่านเข้ามาในความหมายคอลัมน์แทนที่จะเรียกฟังก์ชั่นของตัวเองด้วยcallable uuid.uuid4 uuid.uuid4()มิฉะนั้นคุณจะมีค่าสเกลาร์เท่ากันสำหรับอินสแตนซ์ทั้งหมดของคลาสนี้ รายละเอียดเพิ่มเติมที่นี่ :

นิพจน์สเกลาร์ Python ที่เรียกได้หรือ ColumnElement ที่แสดงถึงค่าเริ่มต้นสำหรับคอลัมน์นี้ซึ่งจะถูกเรียกใช้เมื่อแทรกหากคอลัมน์นี้ไม่ได้ระบุไว้ในส่วนคำสั่ง VALUES ของส่วนแทรก


6
ฉันเห็นด้วยกับคุณ คำตอบอื่น ๆ บางคำตอบนั้นยอดเยี่ยมสำหรับฐานข้อมูลอื่น ๆ แต่สำหรับ postgres นี่เป็นทางออกที่สะอาดที่สุด (คุณยังสามารถตั้งค่าเริ่มต้นเป็นuuid.uuid4)
pacha

1
คุณสามารถจัดหา MWE ได้หรือไม่? หรือบางที serializer ใน flask_sqlalchemy เข้าใจประเภท UUID? รหัสใน pastebin ด้านล่างข้อผิดพลาดpastebin.com/hW8KPuYw
Brandon Dube

1
ไม่เป็นไรถ้าคุณต้องการใช้วัตถุ UUID จาก stdlib ให้ทำColumn(UUID(as_uuid=True) ...)
Brandon Dube

1
ขอบคุณ! อาจจะดีถ้าColumnและIntegerถูกนำเข้าที่ด้านบนของข้อมูลโค้ดหรือถูกเปลี่ยนเป็นอ่านdb.Columnและdb.Integer
Greg Sadetsky

1
ไม่จำเป็นต้องมี @nephanth
Filipe Bezerra de Sousa

64

ฉันเขียนสิ่งนี้และโดเมนก็หายไป แต่นี่คือความกล้า ...

ไม่ว่าเพื่อนร่วมงานของฉันที่ให้ความสำคัญกับการออกแบบฐานข้อมูลที่เหมาะสมจะรู้สึกอย่างไรเกี่ยวกับ UUID และ GUID ที่ใช้สำหรับคีย์ฟิลด์ ฉันมักจะพบว่าฉันจำเป็นต้องทำ ฉันคิดว่ามันมีข้อดีกว่าการเพิ่มอัตโนมัติที่ทำให้คุ้มค่า

ฉันได้ปรับปรุงประเภทคอลัมน์ UUID ในช่วงสองสามเดือนที่ผ่านมาและฉันคิดว่าในที่สุดฉันก็มั่นคง

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)))

ฉันเชื่อว่าการจัดเก็บเป็นไบนารี (16 ไบต์) ควรมีประสิทธิภาพมากกว่าการแสดงสตริง (36 ไบต์?) และดูเหมือนว่าจะมีข้อบ่งชี้บางอย่างว่าการสร้างดัชนีบล็อก 16 ไบต์ควรมีประสิทธิภาพใน mysql มากกว่าสตริง ฉันจะไม่คาดหวังว่ามันจะแย่ลงอีกต่อไป

ข้อเสียอย่างหนึ่งที่ฉันพบคืออย่างน้อยใน phpymyadmin คุณไม่สามารถแก้ไขระเบียนได้เนื่องจากโดยปริยายพยายามที่จะทำการแปลงอักขระบางประเภทสำหรับ "select * from table where id = ... " และมีปัญหาในการแสดงผลอื่น ๆ

นอกเหนือจากนั้นทุกอย่างดูเหมือนจะทำงานได้ดีดังนั้นฉันจึงโยนมันออกไปที่นั่น แสดงความคิดเห็นหากคุณเห็นข้อผิดพลาดที่ชัดเจน ฉันยินดีรับข้อเสนอแนะสำหรับการปรับปรุง

หากฉันไม่ได้ทำอะไรบางอย่างวิธีแก้ปัญหาข้างต้นจะใช้งานได้หากฐานข้อมูลที่สำคัญมีประเภท UUID หากไม่เป็นเช่นนั้นคุณอาจได้รับข้อผิดพลาดเมื่อสร้างตาราง วิธีแก้ปัญหาที่ฉันคิดขึ้นมาคือกำหนดเป้าหมาย MSSqlServer ในตอนแรกจากนั้นไปที่ MySql ในที่สุดดังนั้นฉันคิดว่าโซลูชันของฉันมีความยืดหยุ่นมากกว่าเล็กน้อยเนื่องจากดูเหมือนว่าจะทำงานได้ดีบน mysql และ sqlite ยังไม่ได้ใส่ใจในการตรวจสอบ postgres เลย


ใช่ฉันโพสต์หลังจากที่ฉันเห็นการอ้างอิงจากคำตอบของยาโคบ
Tom Willis

4
โปรดทราบว่าหากคุณใช้เวอร์ชัน 0.6 ขึ้นไปคำสั่งการนำเข้า MSBinary ในโซลูชันของ Tom ควรเปลี่ยนเป็น "from sqlalchemy.dialects.mysql.base import MSBinary" ที่มา: mail-archive.com/sqlalchemy@googlegroups.com/msg18397.html
Cal Jacobson

2
"ฉันเขียนสิ่งนี้" เป็นลิงก์ที่ตายแล้ว
julx


2
@codeninja postgresql มีประเภท UUID ดั้งเดิมอยู่แล้วดังนั้นเพียงใช้sqlalchemy.dialects.postgresql.UUIDโดยตรง ดูBackend-agnostic GUID Type
cowbert

24

หากคุณพอใจกับคอลัมน์ 'String' ที่มีค่า UUID นี่เป็นวิธีง่ายๆ:

def generate_uuid():
    return str(uuid.uuid4())

class MyTable(Base):
    __tablename__ = 'my_table'

    uuid = Column(String, name="uuid", primary_key=True, default=generate_uuid)

5
อย่าเก็บ UUID เป็นสตริงเว้นแต่คุณจะใช้ฐานข้อมูลแปลก ๆ ที่ไม่รองรับ มิฉะนั้นอาจจัดเก็บข้อมูลทั้งหมดของคุณเป็นสตริง ... ;)
นิค

@ ทำไม? ข้อเสียคืออะไร?
rayepps

6
@rayepps - มีข้อเสียอยู่หลายประการ - ไม่สำคัญบางประการ: ขนาด - สตริง uuid ใช้พื้นที่สองเท่า - 16 ไบต์เทียบกับ 32 ตัวอักษร - ไม่รวมถึงรูปแบบใด ๆ เวลาในการประมวลผล - ไบต์ที่มากขึ้น = เวลาประมวลผลของ CPU มากขึ้นเมื่อชุดข้อมูลของคุณใหญ่ขึ้น รูปแบบสตริง uuid แตกต่างกันไปตามภาษาเพิ่มคำแปลที่จำเป็นเพิ่มเติม ง่ายกว่าสำหรับคนที่ใช้คอลัมน์ในทางที่ผิดเพราะคุณสามารถใส่อะไรก็ได้ในสิ่งที่ไม่ใช่ uuids นั่นน่าจะเพียงพอสำหรับการเริ่มต้น
นิค

คุณไม่ควรใช้ Strings เป็นคอลัมน์สำหรับ uuid สำหรับปัญหาด้านประสิทธิภาพ แนะนำให้ใช้ไบนารี (16) มากกว่า
Cyril N.

19

ฉันใช้แพ็คเกจUUIDTypeจากSQLAlchemy-Utils: 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'>)
CodeTrooper

พวกคุณได้รับข้อผิดพลาด: NameError: name 'sqlalchemy_utils' is not defined?
Walter

1
SQLAlchemy-Utilsเป็นแพ็คเกจของบุคคลที่สามคุณต้องติดตั้งก่อน:pip install sqlalchemy-utils
Berislav Lopac

นี่คือวิธีที่จะไปแม้ว่าการย้ายข้อมูลของคุณจำเป็นต้องมีบัญชีหรือระบบที่มีค่า UUID เทียบกับ CHAR / BINARY สำหรับ uuids
rjurney

9

เนื่องจากคุณใช้ Postgres สิ่งนี้ควรใช้งานได้:

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)

1
นี่ควรเป็นคำตอบเดียวที่ได้รับการยอมรับสำหรับนักพัฒนาที่ใช้ฐานข้อมูล PostgreSQL
José L. Patiño

5

นี่คือแนวทางตามGUID ที่ไม่เชื่อเรื่องพระเจ้าของแบ็กเอนด์จากเอกสาร SQLAlchemy แต่ใช้ฟิลด์ BINARY เพื่อจัดเก็บ UUID ในฐานข้อมูลที่ไม่ใช่ 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)

1
การใช้งานจะเป็นอย่างไร?
CodeTrooper

3

ในกรณีที่มีใครสนใจฉันใช้คำตอบของ Tom Willis แต่พบว่ามีประโยชน์ในการเพิ่มสตริงในการแปลง uuid.UUID ในเมธอด 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

-19

คุณสามารถลองเขียนประเภทที่กำหนดเองตัวอย่างเช่น:

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แล้วยังมีรายการบล็อกนี้ด้วย มันมีลักษณะคล้ายยกเว้นว่ามันคลาสย่อยแทนtypes.TypeDecorator types.TypeEngineแนวทางใดมีข้อได้เปรียบหรือเสียเปรียบอีกวิธีหนึ่งหรือไม่?
Jacob Gabrielson

11
สิ่งนี้ใช้ไม่ได้ผลเป็นเพียงงานที่ตัดแล้ววางจากตัวอย่างประเภทจำลองจากเอกสาร คำตอบของ Tom Willis ด้านล่างดีกว่ามาก
Jesse Dhillon

ไม่จำเป็นต้องมีdefault=?? เช่นColumn('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)
iJames

จุดเชื่อมโยงไปยัง "ไม่พบหน้า" docs.sqlalchemy.org/en/13/core/...เป็น propably ใกล้กับคนเก่า
barbsan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.