web-dev-qa-db-fra.com

Comment puis-je utiliser des UUID dans SQLAlchemy?

Existe-t-il un moyen de définir une colonne (clé primaire) comme UUID in SQLAlchemy si vous utilisez PostgreSQL = (Postgres)?

60
Vasil

Le dialecte sqlalchemy postgres prend en charge les colonnes UUID. C'est facile (et la question est spécifiquement postgres) - je ne comprends pas pourquoi les autres réponses sont si compliquées.

Voici un exemple:

from sqlalchemy.dialects.postgresql import UUID
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class Foo(db.Model):
    id = Column(Integer, primary_key=True)
    uuid = Column(UUID(as_uuid=True), unique=True, nullable=False)
69
JDiMatteo

j'ai écrit ceci et le domaine a disparu mais voici le courage ...

Indépendamment de la façon dont mes collègues qui se soucient vraiment de la conception appropriée de la base de données pensent des UUID et des GUID utilisés pour les domaines clés. Je trouve souvent que je dois le faire. Je pense qu'il a certains avantages par rapport à l'auto-incrémentation qui en valent la peine.

J'ai affiné un type de colonne UUID au cours des derniers mois et je pense que je l'ai enfin solide.

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

Je pense que le stockage en binaire (16 octets) devrait être plus efficace que la représentation sous forme de chaîne (36 octets?), Et il semble y avoir une indication que l'indexation de blocs de 16 octets devrait être plus efficace dans mysql que les chaînes. Je ne m'attendrais pas à ce que ce soit pire de toute façon.

Un inconvénient que j'ai trouvé est qu'au moins dans phpymyadmin, vous ne pouvez pas modifier les enregistrements car il essaie implicitement de faire une sorte de conversion de caractères pour le "select * from table where id = ..." et il y a divers problèmes d'affichage.

A part ça, tout semble bien fonctionner, alors je le lance. Laissez un commentaire si vous voyez une erreur flagrante avec. Je me réjouis de toute suggestion pour l'améliorer.

Sauf si je manque quelque chose, la solution ci-dessus fonctionnera si la base de données sous-jacente a un type UUID. Si ce n'est pas le cas, vous obtiendrez probablement des erreurs lors de la création de la table. La solution que j'ai trouvée ciblait MSSqlServer à l'origine et est finalement devenue MySql, donc je pense que ma solution est un peu plus flexible car elle semble fonctionner correctement sur mysql et sqlite. Je n'ai pas encore pris la peine de vérifier les postgres.

61
Tom Willis

Voir aussi la recette pour Backend-agnostic GUID Type dans la documentation SQLAlchemy pour les types de colonnes.

32
Kamil Kisiel

Si vous êtes satisfait d'une colonne 'String' ayant une valeur UUID, voici une solution simple:

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

class MyTable(Base):
    __table= 'my_table'

    uuid = Column(String, name="uuid", primary_key=True, default=generate_uuid)
17
Kushal Ahmed
11
Berislav Lopac

Voici une approche basée sur le GUID agnostique backend des documents SQLAlchemy, mais en utilisant un champ BINARY pour stocker les UUID dans des bases de données non 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)
4
zwirbeltier

Au cas où quelqu'un serait intéressé, j'ai utilisé la réponse de Tom Willis, mais j'ai trouvé utile d'ajouter une chaîne à la conversion uuid.UUID dans la méthode 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
3
Nemeth