¿La mejor manera de hacer enumeración en Sqlalchemy?

Estoy leyendo sobre sqlalchemy y vi el siguiente código:

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 sólo un tipo de enumeración?

Related of "¿La mejor manera de hacer enumeración en Sqlalchemy?"

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

Aunque solo recomendaría su uso si su base de datos tiene un tipo de enumeración nativo. De lo contrario, personalmente solo usaría un int.

Los tipos enumerados de Python son directamente aceptables por el tipo SQLAlchemy Enum a partir de SQLAlchemy 1.1 :

 import enum from sqlalchemy import Integer, 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)) 

Tenga en cuenta que arriba, los valores de cadena “uno”, “dos”, “tres” se mantienen, no los valores enteros.

Para versiones anteriores de SQLAlchemy, escribí una publicación 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( '([AZ])', 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() 

Realmente no estoy bien informado en SQLAlchemy pero este enfoque de Paulo me pareció mucho más simple.
No necesitaba descripciones fáciles de usar, así que fui con eso.

Citando a Paulo (espero que no le importe que lo haya enviado aquí):

La colección de dos namedtuple de Python para el rescate. Como su nombre lo indica, una namedtuple es una tupla con cada elemento que tiene un nombre. Como una tupla ordinaria, los artículos son inmutables. A diferencia de una tupla ordinaria, se puede acceder al valor de un elemento a través de su nombre usando la notación de puntos.

Aquí hay una función de utilidad para crear una namedtuple :

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

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

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

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

Ahora podemos usar el project_version namedtuple 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.

Nota: lo siguiente está desactualizado. Debe usar sqlalchemy.types.Enum ahora, como lo recomienda 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 cambié dos cosas:

  • Estoy usando el nombre Python del EnumSymbol también como el nombre en la base de datos, en lugar de usar su valor. Creo que eso es menos confuso. Tener un valor separado sigue siendo útil, por ejemplo, para crear menús emergentes en la interfaz de usuario. La descripción puede considerarse una versión más larga del valor que puede usarse, por ejemplo, para información sobre herramientas.
  • En la receta original, el orden de los EnumSymbols es arbitrario, tanto cuando se repite en Python como cuando se hace un “orden por” en la base de datos. Pero a menudo quiero tener un orden determinado. Así que cambié el orden para que sea alfabético si establece los atributos como cadenas o tuplas, o el orden en que se declaran los valores si establece explícitamente los atributos como EnumSymbols. en clases declarativas.

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 modificada; utiliza la clase OrderedDict disponible 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( '([AZ])', 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())