在Sqlalchemy中使用枚举的最佳方法?
我在学习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')
我应该把'type'设置成一个整数,并在一个库里定义常量吗?还是说我应该直接把'type'设置成一个枚举类型?
8 个回答
我对SQLAlchemy不是很了解,但Paulo的方法对我来说看起来简单多了。
我不需要用户友好的描述,所以我就选择了这个方法。
引用Paulo的话(希望他不介意我在这里转发):
Python的
namedtuple
集合来帮忙。顾名思义,namedtuple
就是一个元组,每个项目都有一个名字。和普通元组一样,namedtuple
里的项目是不可变的。但不同的是,你可以通过名字来访问项目的值,使用点号表示法。这里有一个创建
namedtuple
的工具函数:from collections import namedtuple def create_named_tuple(*values): return namedtuple('NamedTuple', values)(*values)
在值变量前面的
*
是用来“解包”列表中的项目,这样每个项目就会作为单独的参数传递给函数。要创建一个
namedtuple
,只需用需要的值调用上面的函数:>>> project_version = create_named_tuple('alpha', 'beta', 'prod') NamedTuple(alpha='alpha', beta='beta', prod='prod')
现在我们可以使用
project_version
这个namedtuple来指定版本字段的值。class Project(Base): ... version = Column(Enum(*project_version._asdict().values(), name='projects_version')) ...
这个方法对我来说效果很好,比我之前找到的其他解决方案简单多了。
从SQLAlchemy 1.1开始,Python的枚举类型可以直接被SQLAlchemy的Enum类型使用:
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))
需要注意的是,上面提到的字符串值“one”、“two”、“three”是被保存下来的,而不是整数值。
对于旧版本的SQLAlchemy,我写过一篇文章,介绍了如何创建自己的枚举类型(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()
SQLAlchemy 从 0.6 版本开始就有了一个叫做 Enum 的类型:http://docs.sqlalchemy.org/en/latest/core/type_basics.html?highlight=enum#sqlalchemy.types.Enum
不过,我个人建议只有在你的数据库本身支持枚举类型时才使用这个 Enum 类型。否则,我更倾向于直接用整数来代替。