¿Hay alguna forma de definir una columna (clave principal) como UUID en SQLAlchemy si usa PostgreSQL (Postgres)?
python
postgresql
orm
sqlalchemy
uuid
Vasil
fuente
fuente
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 conuuid.uuid4()
. De lo contrario, tendrá el mismo valor escalar para todas las instancias de esta clase. Más detalles aquí :fuente
uuid.uuid4
).Column(UUID(as_uuid=True) ...)
Column
yInteger
se importaran en la parte superior del fragmento de código, o se cambiaran para leerdb.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.
fuente
sqlalchemy.dialects.postgresql.UUID
directamente. consulte Tipo de GUIDSi 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)
fuente
He usado el
UUIDType
delSQLAlchemy-Utils
paquete: http://sqlalchemy-utils.readthedocs.org/en/latest/data_types.html#module-sqlalchemy_utils.types.uuidfuente
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)
fuente
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)
fuente
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
fuente
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), )
fuente
types.TypeDecorator
lugar detypes.TypeEngine
. ¿Alguno de los enfoques tiene una ventaja o una desventaja sobre el otro?default=?
? por ejemploColumn('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)