2010-04-20 6 views
30

Ich lese über sqlalchemy und ich sah Code folgende:Beste Art, in Sqlalchemy enum zu machen?

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

Sollte ich ‚Typ‘ ein int, mit Konstanten in einer Bibliothek zu machen? Oder soll ich einfach Typ enum machen?

Antwort

23

SQLAlchemy hat einen Enum-Typ seit 0,6: http://docs.sqlalchemy.org/en/latest/core/type_basics.html?highlight=enum#sqlalchemy.types.Enum

Obwohl würde ich nur empfehlen es Nutzung ist, wenn Ihre Datenbank eine native Aufzählungstyp hat. Sonst würde ich persönlich einfach einen Int benutzen.

+0

der zweite Link öffnet sich nicht mehr – MajesticRa

+0

@MajesticRa: Danke für den Tipp, ich habe den Link entfernt. Es ist sowieso nicht mehr relevant. Jeder sollte auf vor 0.6 Jahren aktualisiert haben;) – Wolph

45

Typen Python aufgezählt sind direkt akzeptabel durch den SQLAlchemy Enum Typen wie der 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)) 

beachte, dass der String-Wert „eins“, „zwei“, „drei“ beibehalten wird, nicht über die ganzzahlige Werte.

Für ältere Versionen von SQLAlchemy, schrieb ich einen Beitrag, der seinen eigenen Aufzählungstyp erzeugt (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, Link ist noch am Leben, aber der dort veröffentlichte runnable Code scheint nicht mit den neuesten Versionen von SQLA (1.0.9) zu funktionieren. Ist das immer noch deine bevorzugte Methode, Enums zu bearbeiten, @zzzeek? – achiang

+0

Ich habe gerade das Skript unter http://techspot.zzzeek.org/files/2011/decl_enum.py genau so ausgeführt wie es gegen Master ist und es läuft perfekt. – zzzeek

+0

Hallo @zzzeek, ​​das ist, was ich erlebt habe: https://gist.github.com/achiang/8d4a6e3f495084d6761c – achiang

9

Anmerkung: die folgenden veraltet ist. Sie sollten jetzt sqlalchemy.types.Enum verwenden, wie von Wolph empfohlen. Es ist besonders schön, da es seit SQLAlchemy 1.1 mit PEP-435 übereinstimmt.


Ich mag Rezept zzzeek bei http://techspot.zzzeek.org/2011/01/14/the-enum-recipe/, aber ich änderte zwei Dinge:

  • Ich verwende die Python Namen des EnumSymbol auch als Name in der Datenbank, statt dessen Wert zu verwenden. Ich denke, das ist weniger verwirrend. Ein separater Wert ist immer noch nützlich, z. zum Erstellen von Popup-Menüs in der Benutzeroberfläche. Die Beschreibung kann als eine längere Version des Wertes betrachtet werden, der z.B. für Tooltips.
  • Im Originalrezept ist die Reihenfolge der EnumSymbols beliebig, sowohl wenn Sie in Python über sie iterieren, als auch wenn Sie eine "Bestellung von" für die Datenbank ausführen. Aber oft möchte ich eine bestimmte Reihenfolge haben. Also habe ich die Reihenfolge geändert, um alphabetisch zu sein, wenn Sie die Attribute als Zeichenfolgen oder Tupel festlegen, oder die Reihenfolge, in der die Werte deklariert werden, wenn Sie die Attribute explizit als EnumSymbols festlegen - das verwendet denselben Trick wie SQLAlchemy bei der Anordnung der Spalten in DeclarativeBase-Klassen.

Beispiele:

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

Hier ist die modifizierte Rezept; es nutzt die OrderedDict Klasse 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

ich in SQLAlchemy nicht wirklich gut informiert bin, aber this approach by Paulo schien mir viel einfacher.
Ich brauchte keine benutzerfreundlichen Beschreibungen, also ging ich mit.

Paulo Zitiert (ich hoffe, dass er nicht meine es hier nichts ausmacht reposting):

namedtuple Kollektion Python zur Rettung. Wie der Name andeutet, ist ein namedtuple ein Tupel, wobei jedes Element einen Namen hat. Wie ein gewöhnliches Tupel sind die Gegenstände unveränderlich.Im Gegensatz zu einem gewöhnlichen Tupel kann der Wert eines Gegenstandes über seinen Namen unter Verwendung der Punktnotation erreicht werden.

ist die Nutzenfunktion eine namedtuple zum Erstellen:

from collections import namedtuple 

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

Die * bevor die Werte Variable ist für „entpackt“ die Elemente der Liste, so dass jedes Element als Einzel Argument an die übergeben wird Funktion.

Um ein namedtuple, rufen Sie einfach die obige Funktion mit den benötigten Werte zu schaffen:

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

Wir haben jetzt die project_version namedtuple die Werte der Versionsfeld angeben können.

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

Das funktioniert gut für mich und ist so viel einfacher als die anderen Lösungen, die ich zuvor gefunden habe.

Verwandte Themen