Mini Iterable Model Mapper для python для Google App Engine - PullRequest
3 голосов
/ 21 апреля 2011

Я не думаю, что то, что я хочу, существует. Может ли кто-нибудь создать для меня класс мини-картографов barebones? Детальный псевдокод или фактический Python в порядке. Обновление: Простая, рабочая версия внизу поста.

Обновление 2 - 20 июня:

  • более безопасное выполнение: продолжить / прервать / вернуть в iterall () теперь работает.
  • добавлен флаг defer_db для отправки дБ, помещает и удаляет в очереди задач
  • для целей абстракции может указывать функцию фильтра, которую каждая сущность должна пройти, иначе она не будет возвращена при итерации.
  • изменено .bdelete () на .bdel ()

Обновление 3 - 21 июня:

  • исправлена ​​серьезная ошибка, появившаяся в последнем обновлении, которая препятствовала сохранению.

Надеюсь, это полезно кому-то, кроме меня. Я использую его часто, и это удобный промежуточный элемент MapReduce, и когда вам просто нужны курсоры, потому что вы не знаете, с какими результатами вам придется иметь дело.


О чем это?

Mapreduce lib для gae отличная, но я хочу что-нибудь легкое и одноразовое. В учебнике по Python gae часто вы видите, что модели БД перебираются, модифицируются и сохраняются. Я не думаю, что есть больше подобных примеров, потому что, как мы знаем, это очень неэффективно и вызывает хранилище данных один раз для каждого цикла вместо пакетной обработки. Мне нравится этот интерфейс, и я часто нуждаюсь в простом и быстром способе работы с моими моделями БД.

Как бы это выглядело?

Использование

  1. Импорт класса.
  2. Скажите, какую модель вы хотите отобразить на
  3. предоставить дополнительные фильтры запросов
  4. получить объект итератора
  5. Зацикливайтесь, зная, что вы не делаете тысячи ненужных вызовов в БД.

За кулисами

Здесь я нуждаюсь в твоей помощи, потому что чувствую, что у меня над головой.

Генератор (Я никогда не использовал генераторы, и я их только понимаю) Объектная группировка захватывает элементы хранилища данных (сколько можно захватить? Есть ли жесткое ограничение или оно зависит от размера элемента? ) и представляет их итеративно. Как только MAX_AMOUNT batch_size достигнут, пакетное сохранение элементов в хранилище данных и плавный захват следующего пакета (курсора).

Одна вещь, которую я рассматривал, заключалась в использовании defer для сохранения элементов в БД, с намерением сэкономить некоторое время, если мы перебираем множество элементов. Возможным недостатком может быть то, что в следующем разделе кода ожидается, что карта будет завершена. поэтому я думаю, что было бы хорошо, чтобы флаг 'defer_db' был установлен или проигнорирован в зависимости от предпочтений пользователя. Если вы ожидаете только небольшое количество предметов, вы не установили бы флаг отсрочки.

Заключение

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

Примеры

Те же функции запроса

country_mim = MIM(CountryModels.all()).filter("spoken_language =", "French")
country_mim.order("population")

вложенная итерация

some_mim = MIM(SomeModel.all())
for x in some_mim.iterall():
    if x.foo == 'ham sandwich':
        sandwich_mim = MIM(MySandwiches.all())
        for sandwich in sandwich_mim.iterall():
            if 'ham' in sandwich.ingredients:
                print 'yay'

Пакетное сохранение и удаление

country_mim = MIM(CountryModels.all()).order("drinking_age")
for country in country_mim.iterall():
    if country.drinking_age > 21:   # these countries should be nuked from orbit
        country_mim.bdel(country)   # delete
    if country.drinking_age == 18:
        country.my_thoughts = "god bless you foreigners"
        country_mim.bput(country)   # save
    if country.drinking_age < 10:   # panic
        country.my_thoughts = "what is this i don't even..."
        country_mim.bput(country)
        break   # even though we panicked, the bput still resolves

Некоторые коды: MiniIterMapper.py

Я использую этот код уже несколько дней недель, и все, кажется, в порядке. Отсрочка не была включена. Код фасада запроса был украден (с разрешения) из большого модуля PagedQuery . Поддерживает пакетное сохранение и пакетное удаление.

import google.appengine.ext.db as db
from google.appengine.ext.deferred import defer

class MIM(object):
    """
    All standard Query functions (filter, order, etc) supported*. Default batch
    size is 100. defer_db=True will cause put and delete datastore operations to
    be deferred. allow_func accepts any function you wish and only the entities
    that cause the function to return a true value will be returned during
    iterall(). Using break/continue/return while iterating doesn't cause things
    to explode (like it did in the 1st version).

    * - thanks to http://code.google.com/p/he3-appengine-lib/wiki/PagedQuery
    """

    def __init__(self, query, batch_size=100, defer_db=False, allow_func=None):

        self._query =       query
        self._batch_size =  batch_size
        self._defer_db =    defer_db
        self._allow_func =  allow_func
        self._to_save =     []
        self._to_delete =   []

        # find out if we are dealing with another facade object
        if query.__dict__.has_key('_query'): query_to_check = query._query
        else: query_to_check  = query

        if isinstance(query_to_check, db.Query):        self._query_type = 'Query'
        elif isinstance(query_to_check, db.GqlQuery):   self._query_type = 'GqlQuery'
        else: raise TypeError('Query type not supported: ' + type(query).__name__)

    def iterall(self):
        "Return iterable over all datastore items matching query. Items pulled from db in batches."

        results =               self._query.fetch(self._batch_size) # init query
        savedCursor =           self._query.cursor()                # init cursor

        try:
            while results:

                for item in results:
                    if self._allow_func:
                        if self._allow_func(item):
                            yield item
                    else:
                        yield item

                if len(results) ==  self._batch_size:
                    results =       self._query.with_cursor(savedCursor).fetch(self._batch_size)
                    savedCursor =   self._query.cursor()

                else:                   # avoid additional db call if we don't have max amount
                    results =       []  # while loop will end, and go to else section.
            else:
                self._finish()
        except GeneratorExit:
            self._finish()

    def bput(self, item):
        "Batch save."
        self._to_save.append(item)
        if len(self._to_save) >= self._batch_size:
            self._bput_go()

    def bdel(self, item):
        "Batch delete."
        self._to_delete.append(item)
        if len(self._to_delete) >= self._batch_size:
            self._bdel_go()

    def _bput_go(self):
        if self._defer_db:
            defer(db.put, self._to_save)
        else: db.put(self._to_save)
        self._to_save = []

    def _bdel_go(self):
        if self._defer_db:
            defer(db.delete, self._to_delete)
        else: db.delete(self._to_delete)
        self._to_delete = []

    def _finish(self):
        "When done iterating through models, could be that the last few remaining weren't put/deleted yet."
        if self._to_save:   self._bput_go()
        if self._to_delete: self._bdel_go()

    # FACADE SECTION >>>

    def fetch(self, limit, offset=0):
        return self._query.fetch(limit,offset)

    def filter(self, property_operator, value):
        self._check_query_type_is('Query')
        self._query = self._query.filter(property_operator, value)
        return self

    def order(self, property):
        self._check_query_type_is('Query')
        self._query.order(property)
        return self

    def ancestor(self, ancestor):
        self._check_query_type_is('Query')
        self._query.ancestor(ancestor)
        return self

    def count(self, limit=1000):
        return self._query.count(limit)

    def _check_query_type_is(self, required_query_type):
        if self._query_type != required_query_type:
            raise TypeError('Operation not allowed for query type ('\
                            + type(self._query).__name__)

1 Ответ

1 голос
/ 21 апреля 2011

Почему вы не хотите использовать Mapreduce? Он предназначен именно для этого варианта использования, уже делает все, что вы хотите, и может быть вызван программно. «Легкий» - это очень расплывчатый термин, но я не знаю ни одной причины, по которой библиотека mapreduce не совсем подходит для вашей задачи - и есть очень мало причин дублировать эту функциональность.

...