Existe uma maneira de definir uma coluna (chave primária) como um UUID em SQLAlchemy se estiver usando PostgreSQL (Postgres)?
python
postgresql
orm
sqlalchemy
uuid
Vasil
fonte
fonte
Respostas:
O dialeto sqlalchemy postgres oferece suporte a colunas UUID. Isso é fácil (e a questão é especificamente postgres) - eu não entendo por que as outras respostas são tão complicadas.
Aqui está um exemplo:
from sqlalchemy.dialects.postgresql import UUID from flask_sqlalchemy import SQLAlchemy import uuid db = SQLAlchemy() class Foo(db.Model): id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True)
Tenha cuidado para não deixar de passar o
callable
uuid.uuid4
para a definição de coluna, em vez de chamar a própria função comuuid.uuid4()
. Caso contrário, você terá o mesmo valor escalar para todas as instâncias desta classe. Mais detalhes aqui :fonte
uuid.uuid4
).Column(UUID(as_uuid=True) ...)
Column
eInteger
fosse importado no topo do snippet de código ou alterado para lerdb.Column
edb.Integer
Eu escrevi isso e o domínio se foi, mas aqui está a coragem ....
Independentemente de como meus colegas que realmente se preocupam com o design de banco de dados adequado se sentem sobre UUIDs e GUIDs usados para campos-chave. Freqüentemente acho que preciso fazer isso. Acho que tem algumas vantagens sobre o incremento automático que o fazem valer a pena.
Venho refinando um tipo de coluna UUID nos últimos meses e acho que finalmente entendi.
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)))
Acredito que armazenar como binário (16 bytes) deve acabar sendo mais eficiente do que a representação de string (36 bytes?), E parece haver alguma indicação de que a indexação de blocos de 16 bytes deve ser mais eficiente no mysql do que strings. Eu não esperava que fosse pior de qualquer maneira.
Uma desvantagem que descobri é que pelo menos no phpymyadmin, você não pode editar registros porque ele tenta implicitamente fazer algum tipo de conversão de caracteres para "select * from table where id = ..." e há diversos problemas de exibição.
Fora isso, tudo parece funcionar bem, então estou jogando fora por aí. Deixe um comentário se você vir um erro gritante nele. Eu agradeço qualquer sugestão para melhorá-lo.
A menos que esteja faltando alguma coisa, a solução acima funcionará se o banco de dados subjacente tiver um tipo UUID. Do contrário, você provavelmente obterá erros quando a tabela for criada. A solução que encontrei era originalmente destinada ao MSSqlServer e depois fui para o MySql no final, então acho que minha solução é um pouco mais flexível, pois parece funcionar bem no mysql e no sqlite. Ainda não me preocupei em verificar o postgres.
fonte
sqlalchemy.dialects.postgresql.UUID
diretamente. consulte Tipo de GUID agnóstico de backendSe você está satisfeito com uma coluna 'String' com valor UUID, aqui vai uma solução simples:
def generate_uuid(): return str(uuid.uuid4()) class MyTable(Base): __tablename__ = 'my_table' uuid = Column(String, name="uuid", primary_key=True, default=generate_uuid)
fonte
Usei o
UUIDType
doSQLAlchemy-Utils
pacote: http://sqlalchemy-utils.readthedocs.org/en/latest/data_types.html#module-sqlalchemy_utils.types.uuidfonte
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
é um pacote de terceiros, você precisa instalá-lo primeiro:pip install sqlalchemy-utils
Como você está usando o Postgres, isso deve 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)
fonte
Aqui está uma abordagem baseada no GUID agnóstico de Backend dos documentos SQLAlchemy, mas usando um campo BINARY para armazenar os UUIDs em bancos de dados não 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)
fonte
Caso alguém esteja interessado, estou usando a resposta de Tom Willis, mas achei útil adicionar uma string à conversão uuid.UUID no 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
fonte
Você pode tentar escrever um tipo personalizado , por exemplo:
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), )
fonte
types.TypeDecorator
vez detypes.TypeEngine
. Qualquer uma das abordagens tem uma vantagem ou desvantagem sobre a outra?default=?
? por exemploColumn('id', UUID(), primary_key=True, default=<someautouuidgeneratingthing>)