Лучший способ сделать перечисление в Sqlalchemy? - PullRequest
47 голосов
/ 20 апреля 2010

Я читаю о 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' int с константами в библиотеке? Или я должен просто сделать тип enum?

Ответы [ 4 ]

74 голосов
/ 24 мая 2011

Перечислимые типы Python напрямую принимаются типом перечисления SQLAlchemy с 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))

Обратите внимание, что выше сохраняются строковые значения «один», «два», «три», а не целые значения.

Для более старых версий SQLAlchemy я написал пост, в котором создается собственный тип Enumerated (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()
31 голосов
/ 20 апреля 2010

SQLAlchemy имеет тип Enum начиная с 0.6: http://docs.sqlalchemy.org/en/latest/core/type_basics.html?highlight=enum#sqlalchemy.types.Enum

Хотя я бы порекомендовал использовать его только в том случае, если ваша база данных имеет собственный тип enum. В противном случае я бы лично использовал int.

12 голосов
/ 25 июня 2013

Я не очень разбираюсь в SQLAlchemy, но этот подход Пауло показался мне намного проще.
Мне не нужно было удобных описаний, поэтому я пошел с этим.

Цитируя Пауло (надеюсь, он не против, чтобы я разместил его здесь):

Коллекция 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 для указания значений поля версии.

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

Это прекрасно работает для меня и намного проще, чем другие решения, которые я нашел ранее.

10 голосов
/ 28 октября 2011

Примечание: следующее устарело. Вы должны использовать sqlalchemy.types.Enum сейчас, как рекомендует Wolph. Это особенно приятно, поскольку оно соответствует PEP-435, начиная с SQLAlchemy 1.1.


Мне нравится рецепт zzzeek на http://techspot.zzzeek.org/2011/01/14/the-enum-recipe/,, но я изменил две вещи:

  • Я использую имя Python для EnumSymbol также в качестве имени в базе данных, вместо того, чтобы использовать его значение. Я думаю, что это менее запутанно. Наличие отдельного значения все еще полезно, например, для создания всплывающих меню в пользовательском интерфейсе. Описание может рассматриваться как более длинная версия значения, которое можно использовать, например, для всплывающих подсказок.
  • В оригинальном рецепте порядок EnumSymbols произвольный, как при выполнении итерации по ним в Python, так и при выполнении «упорядочения по» в базе данных. Но часто я хочу иметь определенный порядок. Поэтому я изменил порядок на алфавитный, если вы устанавливаете атрибуты как строки или кортежи, или порядок, в котором значения объявляются, если вы явно устанавливаете атрибуты как 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")

Вот модифицированный рецепт; он использует класс OrderedDict, доступный в 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())
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...