Чи є спосіб визначити стовпець (первинний ключ) як UUID в SQLAlchemy, якщо використовується PostgreSQL (Postgres)?
Чи є спосіб визначити стовпець (первинний ключ) як UUID в SQLAlchemy, якщо використовується PostgreSQL (Postgres)?
Відповіді:
Діалект postlares sqlalchemy підтримує стовпці UUID. Це легко (і питання конкретно postgres) - я не розумію, чому всі інші відповіді такі складні.
Ось приклад:
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 вставки.
uuid.uuid4
).
Column(UUID(as_uuid=True) ...)
Column
і Integer
були завезені в верхній частині фрагмента коду, або були змінені , щоб читати db.Column
іdb.Integer
Я написав це, і домен зник, але ось мужество ....
Незалежно від того, як мої колеги, які дійсно піклуються про правильний дизайн бази даних, ставляться до 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.
sqlalchemy.dialects.postgresql.UUID
безпосередньо. див. тип
Якщо вас влаштовує стовпець "Рядок", що має значення 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)
Я використовував 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'>)
NameError: name 'sqlalchemy_utils' is not defined
:?
SQLAlchemy-Utils
є стороннім пакетом, спершу його потрібно встановити:pip install sqlalchemy-utils
Оскільки ви використовуєте 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)
Ось підхід, заснований на агностичному GUID Backend з документів 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)
Якщо комусь цікаво, я використовую відповідь Тома Вілліса, але виявив корисним додати рядок до перетворення 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
Ви можете спробувати написати власний тип , наприклад:
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
замість types.TypeEngine
. Чи має підхід перевагу чи недолік перед іншим?
default=?
? наприкладColumn('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)