¿Hay alguna forma de definir una columna (clave principal) como UUID en SQLAlchemy si usa PostgreSQL (Postgres)?
¿Hay alguna forma de definir una columna (clave principal) como UUID en SQLAlchemy si usa PostgreSQL (Postgres)?
Respuestas:
El dialecto sqlalchemy postgres admite columnas UUID. Esto es fácil (y la pregunta es específicamente postgres): no entiendo por qué las otras respuestas son tan complicadas.
Aquí hay un ejemplo:
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)
Tenga cuidado de no pasar por alto la callable
uuid.uuid4
definición de la columna, en lugar de llamar a la función con uuid.uuid4()
. De lo contrario, tendrá el mismo valor escalar para todas las instancias de esta clase. Más detalles aquí :
Una expresión escalar, invocable de Python o ColumnElement que representa el valor predeterminado para esta columna, que se invocará al insertarla si esta columna no se especifica en la cláusula VALUES de la inserción.
uuid.uuid4
).
Column(UUID(as_uuid=True) ...)
Column
y Integer
se importaran en la parte superior del fragmento de código, o se cambiaran para leer db.Column
ydb.Integer
Escribí esto y el dominio se ha ido, pero aquí están las agallas ...
Independientemente de cómo se sientan mis colegas que realmente se preocupan por el diseño adecuado de la base de datos sobre los UUID y los GUID utilizados para los campos clave. A menudo encuentro que necesito hacerlo. Creo que tiene algunas ventajas sobre el autoincremento que hacen que valga la pena.
He estado refinando un tipo de columna UUID durante los últimos meses y creo que finalmente lo tengo sólido.
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)))
Creo que almacenar como binario (16 bytes) debería terminar siendo más eficiente que la representación de cadena (36 bytes?), Y parece haber alguna indicación de que indexar bloques de 16 bytes debería ser más eficiente en mysql que las cadenas. De todos modos, no esperaría que fuera peor.
Una desventaja que he encontrado es que al menos en phpymyadmin, no puede editar registros porque implícitamente intenta hacer algún tipo de conversión de caracteres para "seleccionar * de la tabla donde id = ..." y hay varios problemas de visualización.
Aparte de eso, todo parece funcionar bien, así que lo estoy tirando por ahí. Deje un comentario si ve un error evidente con él. Agradezco cualquier sugerencia para mejorarlo.
A menos que me falte algo, la solución anterior funcionará si la base de datos subyacente tiene un tipo de UUID. Si no es así, es probable que obtenga errores cuando se cree la tabla. La solución que se me ocurrió originalmente tenía como objetivo MSSqlServer y luego fui MySql al final, así que creo que mi solución es un poco más flexible, ya que parece funcionar bien en mysql y sqlite. Todavía no me he molestado en comprobar Postgres.
sqlalchemy.dialects.postgresql.UUID
directamente. consulte Tipo de GUID
Si está satisfecho con una columna 'Cadena' que tiene un valor UUID, aquí tiene una solución simple:
def generate_uuid():
return str(uuid.uuid4())
class MyTable(Base):
__tablename__ = 'my_table'
uuid = Column(String, name="uuid", primary_key=True, default=generate_uuid)
He usado el UUIDType
del SQLAlchemy-Utils
paquete: 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
es un paquete de terceros, primero debe instalarlo:pip install sqlalchemy-utils
Como está usando Postgres, esto debería funcionar:
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)
Aquí hay un enfoque basado en el GUID independiente del backend de los documentos de SQLAlchemy, pero usando un campo BINARIO para almacenar los UUID en bases de datos que no son 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)
En caso de que alguien esté interesado, he estado usando la respuesta de Tom Willis, pero me resultó útil agregar una cadena a la conversión uuid.UUID en el método 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
Podría intentar escribir un tipo personalizado , por ejemplo:
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
lugar de types.TypeEngine
. ¿Alguno de los enfoques tiene una ventaja o una desventaja sobre el otro?
default=?
? por ejemploColumn('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)