2010-04-20 13 views
30

que estoy leyendo y vi sqlalchemy siguiente código:¿La mejor manera de hacer enum en 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') 

¿Debo hacer 'tipo' un int, con constantes en una biblioteca? ¿O debería hacer solo hacer un tipo de enumeración?

Respuesta

23

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

Aunque sólo recomendaría es el uso de datos si ésta tiene un tipo de enumeración nativa. De lo contrario, yo personalmente solo usaría un int.

+0

segundo enlace ya no se abre – MajesticRa

+0

@MajesticRa: gracias por la sugerencia, he eliminado el enlace. Ya no es relevante de todos modos. Todos deberían haberse actualizado a 0,6 años atrás;) – Wolph

45

tipos enumerados de Python son directamente aceptable por el tipo SQLAlchemy Enum como de 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)) 

Nota que anteriormente, los valores de cadena "uno", "dos", "tres" se conservan, y no el valores enteros.

Para versiones anteriores de SQLAlchemy, me ha escrito una entrada que crea su propio tipo enumerado (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, el enlace sigue vivo, pero el código ejecutable publicado allí no parece funcionar con las versiones más recientes de SQLA (1.0.9). ¿Sigue siendo este su método preferido para manejar enums, @zzzeek? – achiang

+0

Acabo de ejecutar el script en http://techspot.zzzeek.org/files/2011/decl_enum.py exactamente como está en contra de master y funciona perfectamente. – zzzeek

+0

Hola @zzzeek, ​​esto es lo que experimenté: https://gist.github.com/achiang/8d4a6e3f495084d6761c – achiang

9

Nota: la siguiente es obsoleta. Debería usar sqlalchemy.types.Enum ahora, según lo recomendado por Wolph. Es particularmente bueno ya que cumple con PEP-435 desde SQLAlchemy 1.1.


me gusta la receta de zzzeek en http://techspot.zzzeek.org/2011/01/14/the-enum-recipe/, pero he cambiado dos cosas:

  • que estoy usando el nombre del pitón de la EnumSymbol también como el nombre en la base de datos, en lugar de utilizar su valor. Creo que eso es menos confuso Tener un valor separado sigue siendo útil, p. para crear menús emergentes en la interfaz de usuario. La descripción se puede considerar como una versión más larga del valor que se puede usar, p. para información sobre herramientas.
  • En la receta original, el orden de los EnumSymbols es arbitrario, tanto cuando itera sobre ellos en Python como cuando hace un "pedido por" en la base de datos. Pero a menudo quiero tener un orden determinado. Así que cambié el orden para que fuera alfabético si estableces los atributos como cadenas o tuplas, o el orden en el que se declaran los valores si estableces explícitamente los atributos como EnumSymbols; esto es usando el mismo truco que SQLAlchemy cuando ordena las Columnas en las clases DeclarativeBase.

Ejemplos:

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

Aquí está la receta modificado; se utiliza la clase OrderedDict disponibles en 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

No estoy muy bien informado en SQLAlchemy pero this approach by Paulo parecía mucho más simple para mí.
No necesité descripciones fáciles de usar, así que lo hice.

Citando Paulo (espero que no le importa mi volver a colocar aquí): Colección

de Python namedtuple al rescate. Como su nombre lo indica, un namedtuple es una tupla con cada elemento que tiene un nombre. Como una tupla ordinaria, los elementos son inmutables.A diferencia de una tupla común, se puede acceder al valor de un elemento a través de su nombre usando la notación de puntos.

Aquí es una función de utilidad para la creación de un namedtuple:

from collections import namedtuple 

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

El * antes de la variable valores es para “desempacar” los elementos de la lista de modo que cada elemento se pasa como argumento individual a la función.

Para crear un namedtuple, simplemente invocar la función anterior con los necesarios valores:

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

Ahora podemos usar el namedtuple project_version para especificar los valores del campo de versión.

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

Esto funciona muy bien para mí y es mucho más simple que las otras soluciones que encontré anteriormente.

Cuestiones relacionadas