SQLAlchemy Отображение нескольких столбцов в одно свойство - PullRequest
0 голосов
/ 29 января 2019

Я создаю веб-приложение в Python 3, используя Flask & SQLAlchemy (через Flask-SQLAlchemy; с MySQL или SQLite), и я столкнулся с ситуацией, в которой я хотел бы сослаться на одно свойство в моемкласс модели, который инкапсулирует несколько столбцов в моей базе данных.Я довольно хорошо разбираюсь в MySQL, но это мой первый настоящий опыт освоения SQLAlchemy за рамками основ.Чтение документов, поиск SO и поиск в Google привели меня к двум возможным решениям: гибридные атрибуты ( документы ) или составные столбцы ( документы ).

Мой вопрос: каковы последствия использования каждого из них, и какое из них является подходящим решением для моей ситуации?Ниже приведен пример кода, который представляет собой фрагмент того, что я делаю.


Справочная информация: я разрабатываю приложение для отслеживания и сортировки фотографий и имею таблицу БД, в которой хранюметаданные для этих фотографий, в том числе, когда была сделана фотография.Так как фотографии сделаны в определенном месте, дата и время съемки имеют связанный часовой пояс.Поскольку SQL общеизвестно любит / ненавидит отношения с часовыми поясами, я решил записать, когда фотография была сделана в двух столбцах: дата-время, хранящее дату и время, и строка, хранящая имя часового пояса.(Я бы хотел обойти неизбежные дебаты о том, как хранить даты и время с учетом часового пояса в SQL, пожалуйста.) Что мне нужно, так это один параметр в классе модели, который я могу использовать для получения правильного объекта datetime Python,и что я также могу установить, как любой другой столбец.

Вот моя таблица:

class Photo(db.Model):

    __tablename__ = 'photos'

    id = db.Column(db.Integer, primary_key=True)
    ...
    taken_dt = db.Column(db.datetime, nullable=False)
    taken_tz = db.Column(db.String(64), nullable=False)
    ...

Вот то, что я использую гибридный параметр (добавленный к классу выше, код datetime / pytzpsuedocode):

    @hybrid_parameter
    def taken(self):
        return datetime.datetime(self.taken_dt, self.taken_tz)

    @taken.setter(self, dt):
        self.taken_dt = dt
        self.taken_tz = dt.tzinfo

Оттуда я не совсем уверен, что еще мне нужно на пути @taken.expression или @taken.comparator, или почему я бы выбрал одно из другого.

Вот что я использую, используя составной столбец (опять же, добавленный в вышеупомянутый класс, код datetime / pytz - psuedocode):

    taken = composite(DateTimeTimeZone._make, taken_dt, taken,tz)

class DateTimeTimeZone(object):

    def __init__(self, dt, tz):
        self.dt = dt
        self.tz = tz

    @classmethod
    def from_db(cls, dt, tz):
        return DateTimeTimeZone(dt, tz)

    @classmethod
    def from_dt(cls, dt):
        return DateTimeTimeZone(dt, dt.tzinfo)

    def __composite_values__(self):
        return (self.dt, self.tz)

    def value(self): 
        #This is here so I can get the actual datetime.datetime object
        return datetime.datetime(self.dt, self.tz)

Казалось бы, этот метод имеет приличное количество дополнительныхнакладные расходы, и я не могу придумать способ установить его так, как если бы я использовал любой другой столбец непосредственно из объекта datetime.datetime, не создавая экземпляр объекта-значения сначала, используя .from_dt.

Любые указания относительно того, если я 'я иду внизнеправильный путь здесь будет приветствоваться.Спасибо!

1 Ответ

0 голосов
/ 29 января 2019

TL; DR: изучите подключение AttributeEvent к вашему столбцу и проверьте, нет ли экземпляров datetime, для которых установлен атрибут tz, а затем верните объект DateTimeTimeZone.Если вы посмотрите на документы SQLAlchemy для событий атрибутов , вы увидите, что вы можете сказать SQLAlchemy прослушать событие с набором атрибутов и вызвать для этого свой код.Там вы можете вносить любые изменения в значение, которое вам нужно.Однако вы не можете получить доступ к другим атрибутам класса в это время.Я еще не пробовал это в сочетании с композитами, поэтому я не знаю, будет ли это называться до или после преобразования типов композита.Вам придется попробовать.

edit: Это все о том, чего вы хотите достичь.AttributeEvent может помочь вам в согласованности ваших данных, а hybrid_property и друзья сделают ваши запросы более легкими.Вы должны использовать каждый из них для его предполагаемого варианта использования.

Более подробное обсуждение различий между различными решениями:

hybrid_attribute и composite - два совершенно разных зверя.Чтобы понять hybrid_attribute, сначала нужно понять, что такое column_property и что он может делать.

1) column_property

Этот элемент помещен в маппер и может содержать любое значение selectable.Поэтому, если вы поместите конкретный суб-выбор в column_property, вы сможете получить к нему доступ только для чтения , как если бы это был конкретный столбец.Расчет делается на лету.Вы даже можете использовать его для поиска записей.SQLAlchemy создаст правильный выбор, содержащий ваш суб-выбор для вас.

Пример:

class User(Base):
   id = Column(Integer, primary_key=True)
   first_name = Column(Unicode)
   last_name = Column(Unicode)

   name = column_property(first_name + ' ' + last_name)
   category = column_property(select([CategoryName.name])
                              .select_from(Category.__table__
                                           .join(CategoryName.__table__))
                              .where(Category.user_id == id))

db.query(User).filter(User.name == 'John Doe').all()

db.query(User).filter(User.category == 'Paid').all()

Как вы можете видеть, это может упростить много кода, но нужноБудьте осторожны, подумайте о влиянии на производительность.

2) hybrid_method и hybrid_attribute

A hybrid_attribute аналогичен column_property, но может вызывать другой путь кода, когда вы находитесь в контекст экземпляра .Таким образом, вы можете иметь selectable на уровне класса, но другую реализацию на уровне экземпляра.С помощью hybrid_method вы можете даже параметризовать обе стороны.

3) композитный_атрибут

Это то, что позволяет объединять несколько конкретных столбцов в один логический столбец.Вы должны написать класс для этого логического столбца, чтобы SQLAlchemy мог извлечь из него правильные значения и использовать его в выборках.Это аккуратно интегрируется в структуру запроса и не должно создавать никаких дополнительных проблем.По моему опыту, сценарии использования составных столбцов довольно редки.Ваш вариант использования, кажется, хорошо.Для изменения значений вы всегда можете использовать AttributeEvents.Если вы хотите, чтобы весь экземпляр был доступен, вам нужно будет вызвать MapperEvent до сброса.Это, безусловно, работает, поскольку я использовал это для реализации полностью прозрачной системы отслеживания Audit Trail , в которой каждое значение, измененное в каждой таблице, сохранялось в отдельном наборе таблиц.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...