2010-04-20 450 views
30

我讀關於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') 

我應該讓「類型」 int,使用庫中的常量?或者我應該讓類型變成一個枚舉?

回答

23

SQLAlchemy中有一個枚舉類型,因爲0.6: http://docs.sqlalchemy.org/en/latest/core/type_basics.html?highlight=enum#sqlalchemy.types.Enum

雖然我只會推薦它的使用,如果你的數據庫有一個本地枚舉類型。否則,我會親自使用一個int。

+0

第二個鏈接不再打開 – MajesticRa 2012-10-01 16:33:27

+0

@MajesticRa:謝謝你的提示,我刪除了鏈接。無論如何它已經不再相關了。每個人都應該已經升級到0.6年前;) – Wolph 2012-10-01 17:24:04

45

Python的枚舉類型是直接可接受由SQLAlchemy的枚舉類型的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)) 

注意,上文中,字符串值「一」,「二」,「三」的持續存在,而不是整數值。

對於舊版本的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() 
+0

嗯,鏈接仍然存在,但可運行的代碼發佈在那裏似乎不適用於最新版本的SQLA(1.0.9)。這仍然是您處理枚舉的首選方法,@zzzeek? – achiang 2015-12-08 16:40:04

+0

我剛剛在http://techspot.zzzeek.org/files/2011/decl_enum.py上運行該腳本,與完全相反,它運行完美。 – zzzeek 2015-12-08 21:22:28

+0

嗨@zzzeek,這是我經歷的:https://gist.github.com/achiang/8d4a6e3f495084d6761c – achiang 2015-12-14 04:12:33

9

注意一個帖子:以下是過時的。現在應按照Wolph的建議使用sqlalchemy.types.Enum。從SQLAlchemy 1.1開始,它符合PEP-435特別好。


我喜歡zzzeek的在http://techspot.zzzeek.org/2011/01/14/the-enum-recipe/食譜,但我改變了兩件事情:

  • 我使用的EnumSymbol的Python的名字也爲數據庫的名稱,而不是使用它的價值。我認爲這不那麼令人困惑。具有單獨的值仍然是有用的,例如,用於在UI中創建彈出式菜單。該描述可以被認爲是可以使用的值的更長版本,例如,爲工具提示。
  • 在原始配方中,EnumSymbols的順序是任意的,無論是在Python中迭代它們還是在數據庫上執行「order by」時。但是我經常想要一個確定的順序。因此,如果將屬性設置爲字符串或元組,則將該順序更改爲字母順序,或者如果將屬性明確設置爲EnumSymbols,則聲明值的順序 - 這與SQLAlchemy在命令列時使用的技巧相同在DeclarativeBase類中。

實例:

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

下面是修改的配方;它使用在Python 2.7提供的OrderedDict類:

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

我不是很懂行的SQLAlchemy的,但this approach by Paulo似乎簡單得多給我。
我不需要用戶友好的描述,所以我隨它去了。

引用保羅(我希望他不介意我在這裏重新張貼它):

Python的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')) 
    ... 

這對我很好,比我以前發現的其他解決方案簡單得多。

相關問題