2010-04-20 10 views
30

che sto leggendo di sqlalchemy e ho visto seguente codice:Il modo migliore per fare enum in Sqlalchemy?

employees_table = Table('employees', metadata, 
    Column('employee_id', Integer, primary_key=True), 
    Column('name', String(50)), 
    Column('manager_data', String(50)), 
    Column('engineer_info', String(50)), 
    Column('type', String(20), nullable=False) 
) 

employee_mapper = mapper(Employee, employees_table, \ 
    polymorphic_on=employees_table.c.type, polymorphic_identity='employee') 
manager_mapper = mapper(Manager, inherits=employee_mapper, polymorphic_identity='manager') 
engineer_mapper = mapper(Engineer, inherits=employee_mapper, polymorphic_identity='engineer') 

Devo fare 'tipo' un int, con costanti in una libreria? O dovrei fare solo fare del enum?

risposta

23

SQLAlchemy ha un tipo Enum dal 0,6: http://docs.sqlalchemy.org/en/latest/core/type_basics.html?highlight=enum#sqlalchemy.types.Enum

anche se consiglierei solo che è l'uso se il database ha un tipo enum nativo. Altrimenti personalmente userò solo un int.

+0

secondo link non si apre più – MajesticRa

+0

@MajesticRa: grazie per il suggerimento, ho rimosso il collegamento. Non è più rilevante in ogni caso. Ognuno dovrebbe essere aggiornato a 0.6 anni fa;) – Wolph

45

tipi enumerati Python sono direttamente accettabili dal tipo SQLAlchemy Enum come di SQLAlchemy 1.1:

import enum 

class MyEnum(enum.Enum): 
    one = 1 
    two = 2 
    three = 3 

class MyClass(Base): 
    __tablename__ = 'some_table' 
    id = Column(Integer, primary_key=True) 
    value = Column(Enum(MyEnum)) 

noti che precede, i valori di stringa "uno", "due", "tre" vengono mantenute, non il valori interi.

Per le versioni più vecchie di SQLAlchemy, ho scritto un post che crea il suo proprio tipo enumerato (http://techspot.zzzeek.org/2011/01/14/the-enum-recipe/)

from sqlalchemy.types import SchemaType, TypeDecorator, Enum 
from sqlalchemy import __version__ 
import re 

if __version__ < '0.6.5': 
    raise NotImplementedError("Version 0.6.5 or higher of SQLAlchemy is required.") 

class EnumSymbol(object): 
    """Define a fixed symbol tied to a parent class.""" 

    def __init__(self, cls_, name, value, description): 
     self.cls_ = cls_ 
     self.name = name 
     self.value = value 
     self.description = description 

    def __reduce__(self): 
     """Allow unpickling to return the symbol 
     linked to the DeclEnum class.""" 
     return getattr, (self.cls_, self.name) 

    def __iter__(self): 
     return iter([self.value, self.description]) 

    def __repr__(self): 
     return "<%s>" % self.name 

class EnumMeta(type): 
    """Generate new DeclEnum classes.""" 

    def __init__(cls, classname, bases, dict_): 
     cls._reg = reg = cls._reg.copy() 
     for k, v in dict_.items(): 
      if isinstance(v, tuple): 
       sym = reg[v[0]] = EnumSymbol(cls, k, *v) 
       setattr(cls, k, sym) 
     return type.__init__(cls, classname, bases, dict_) 

    def __iter__(cls): 
     return iter(cls._reg.values()) 

class DeclEnum(object): 
    """Declarative enumeration.""" 

    __metaclass__ = EnumMeta 
    _reg = {} 

    @classmethod 
    def from_string(cls, value): 
     try: 
      return cls._reg[value] 
     except KeyError: 
      raise ValueError(
        "Invalid value for %r: %r" % 
        (cls.__name__, value) 
       ) 

    @classmethod 
    def values(cls): 
     return cls._reg.keys() 

    @classmethod 
    def db_type(cls): 
     return DeclEnumType(cls) 

class DeclEnumType(SchemaType, TypeDecorator): 
    def __init__(self, enum): 
     self.enum = enum 
     self.impl = Enum(
         *enum.values(), 
         name="ck%s" % re.sub(
            '([A-Z])', 
            lambda m:"_" + m.group(1).lower(), 
            enum.__name__) 
        ) 

    def _set_table(self, table, column): 
     self.impl._set_table(table, column) 

    def copy(self): 
     return DeclEnumType(self.enum) 

    def process_bind_param(self, value, dialect): 
     if value is None: 
      return None 
     return value.value 

    def process_result_value(self, value, dialect): 
     if value is None: 
      return None 
     return self.enum.from_string(value.strip()) 

if __name__ == '__main__': 
    from sqlalchemy.ext.declarative import declarative_base 
    from sqlalchemy import Column, Integer, String, create_engine 
    from sqlalchemy.orm import Session 

    Base = declarative_base() 

    class EmployeeType(DeclEnum): 
     part_time = "P", "Part Time" 
     full_time = "F", "Full Time" 
     contractor = "C", "Contractor" 

    class Employee(Base): 
     __tablename__ = 'employee' 

     id = Column(Integer, primary_key=True) 
     name = Column(String(60), nullable=False) 
     type = Column(EmployeeType.db_type()) 

     def __repr__(self): 
      return "Employee(%r, %r)" % (self.name, self.type) 

    e = create_engine('sqlite://', echo=True) 
    Base.metadata.create_all(e) 

    sess = Session(e) 

    sess.add_all([ 
     Employee(name='e1', type=EmployeeType.full_time), 
     Employee(name='e2', type=EmployeeType.full_time), 
     Employee(name='e3', type=EmployeeType.part_time), 
     Employee(name='e4', type=EmployeeType.contractor), 
     Employee(name='e5', type=EmployeeType.contractor), 
    ]) 
    sess.commit() 

    print sess.query(Employee).filter_by(type=EmployeeType.contractor).all() 
+0

Hm, il collegamento è ancora attivo, ma il codice eseguibile pubblicato non sembra funzionare con le versioni recenti di SQLA (1.0.9). Questo è ancora il tuo metodo preferito per gestire le enumerazioni, @zzzeek? – achiang

+0

Ho appena eseguito lo script su http://techspot.zzzeek.org/files/2011/decl_enum.py esattamente come è contro master e funziona perfettamente. – zzzeek

+0

Ciao @zzzeek, ​​questo è quello che ho sperimentato: https://gist.github.com/achiang/8d4a6e3f495084d6761c – achiang

9

Nota: quanto segue è obsoleto. Dovresti usare sqlalchemy.types.Enum ora, come raccomandato da Wolph. È particolarmente bello in quanto conforme a PEP-435 da SQLAlchemy 1.1.


mi piace la ricetta di zzzeek a http://techspot.zzzeek.org/2011/01/14/the-enum-recipe/, ma ho cambiato due cose:

  • sto usando il nome Python del EnumSymbol anche come il nome nel database, invece di usare il suo valore. Penso che sia meno confuso. Avere un valore separato è comunque utile, ad es. per creare menu a comparsa nell'interfaccia utente. La descrizione può essere considerata una versione più lunga del valore che può essere utilizzato, ad es. per suggerimenti.
  • Nella ricetta originale, l'ordine degli EnumSymbols è arbitrario, sia quando si esegue l'iterazione su di essi in Python sia quando si esegue un "ordine per" sul database. Ma spesso voglio avere un ordine determinato. Così ho cambiato l'ordine in modo alfabetico se imposti gli attributi come stringhe o tuple, o l'ordine in cui i valori sono dichiarati se imposti esplicitamente gli attributi come EnumSymbols - questo sta usando lo stesso trucco di SQLAlchemy quando ordina le colonne in classi DeclarativeBase.

Esempi:

class EmployeeType(DeclEnum): 
    # order will be alphabetic: contractor, part_time, full_time 
    full_time = "Full Time" 
    part_time = "Part Time" 
    contractor = "Contractor" 

class EmployeeType(DeclEnum): 
    # order will be as stated: full_time, part_time, contractor 
    full_time = EnumSymbol("Full Time") 
    part_time = EnumSymbol("Part Time") 
    contractor = EnumSymbol("Contractor") 

Ecco la ricetta modificata; si utilizza la classe OrderedDict disponibile in Python 2.7:

import re 

from sqlalchemy.types import SchemaType, TypeDecorator, Enum 
from sqlalchemy.util import set_creation_order, OrderedDict 


class EnumSymbol(object): 
    """Define a fixed symbol tied to a parent class.""" 

    def __init__(self, value, description=None): 
     self.value = value 
     self.description = description 
     set_creation_order(self) 

    def bind(self, cls, name): 
     """Bind symbol to a parent class.""" 
     self.cls = cls 
     self.name = name 
     setattr(cls, name, self) 

    def __reduce__(self): 
     """Allow unpickling to return the symbol linked to the DeclEnum class.""" 
     return getattr, (self.cls, self.name) 

    def __iter__(self): 
     return iter([self.value, self.description]) 

    def __repr__(self): 
     return "<%s>" % self.name 


class DeclEnumMeta(type): 
    """Generate new DeclEnum classes.""" 

    def __init__(cls, classname, bases, dict_): 
     reg = cls._reg = cls._reg.copy() 
     for k in sorted(dict_): 
      if k.startswith('__'): 
       continue 
      v = dict_[k] 
      if isinstance(v, basestring): 
       v = EnumSymbol(v) 
      elif isinstance(v, tuple) and len(v) == 2: 
       v = EnumSymbol(*v) 
      if isinstance(v, EnumSymbol): 
       v.bind(cls, k) 
       reg[k] = v 
     reg.sort(key=lambda k: reg[k]._creation_order) 
     return type.__init__(cls, classname, bases, dict_) 

    def __iter__(cls): 
     return iter(cls._reg.values()) 


class DeclEnum(object): 
    """Declarative enumeration. 

    Attributes can be strings (used as values), 
    or tuples (used as value, description) or EnumSymbols. 
    If strings or tuples are used, order will be alphabetic, 
    otherwise order will be as in the declaration. 

    """ 

    __metaclass__ = DeclEnumMeta 
    _reg = OrderedDict() 

    @classmethod 
    def names(cls): 
     return cls._reg.keys() 

    @classmethod 
    def db_type(cls): 
     return DeclEnumType(cls) 


class DeclEnumType(SchemaType, TypeDecorator): 
    """DeclEnum augmented so that it can persist to the database.""" 

    def __init__(self, enum): 
     self.enum = enum 
     self.impl = Enum(*enum.names(), name="ck%s" % re.sub(
      '([A-Z])', lambda m: '_' + m.group(1).lower(), enum.__name__)) 

    def _set_table(self, table, column): 
     self.impl._set_table(table, column) 

    def copy(self): 
     return DeclEnumType(self.enum) 

    def process_bind_param(self, value, dialect): 
     if isinstance(value, EnumSymbol): 
      value = value.name 
     return value 

    def process_result_value(self, value, dialect): 
     if value is not None: 
      return getattr(self.enum, value.strip()) 
11

Io non sono davvero ben informati in SQLAlchemy ma this approach by Paulo sembrava molto più semplice per me.
Non ho bisogno di descrizioni user-friendly, quindi ci sono andato.

Citando Paolo (spero che non mente il mio reinserire qui): collezione

di Python namedtuple in soccorso. Come suggerisce il nome, una namedtuple è una tupla con ogni oggetto che ha un nome. Come una normale tupla, gli oggetti sono immutabili.A differenza di una normale tupla, è possibile accedere al valore di un oggetto attraverso il suo nome usando la notazione a punti.

Qui è una funzione di utilità per la creazione di un namedtuple:

from collections import namedtuple 

def create_named_tuple(*values): 
    return namedtuple('NamedTuple', values)(*values) 

Il * prima della variabile valori è per “spacchettamento” degli elementi della lista in modo che ogni elemento viene passato come argomento individualmente funzione.

Per creare una namedtuple, basta richiamare la funzione di cui sopra con i necessari valori :

>>> project_version = create_named_tuple('alpha', 'beta', 'prod') 
NamedTuple(alpha='alpha', beta='beta', prod='prod') 

ora possiamo usare il project_version namedtuple per specificare i valori del campo di versione.

class Project(Base): 
    ... 
    version = Column(Enum(*project_version._asdict().values(), name='projects_version')) 
    ... 

Questo funziona per me ed è molto più semplice delle altre soluzioni che ho trovato in precedenza.

Problemi correlati