Потоковый загрузчик данных для App Engine
Недавно я провел много времени ожидая, когда AppEngine Bulk Loader наконец-то закончит свою работу. Пока в вашем приложении нет никаких данных, вы спокойно можете добавлять, изменять и удалять новые объекты в хранилище через Административную консоль, однако когда их становится очень много, работать с таким интерфейсом начинается тяжело.
Официальным инструментом для массовой загрузки данных на сервер является Bulk Loader. Несмотря на то, что он плохо документирован, он является мощным (и медленным) инструментом.
Как описано в статье, на которую я еще сошлюсь, суть его в том, что вы можете создать CSV файл с данными и передать его скрипту bulkload_client.py. Для корректной работы на сервере необходимо установить обработчик, который будет принимать строки в формате CSV и помещать данные в хранилище.
Чтобы сделать это, ваш код должен вызывать метод bulkload.main(), передавая ему по одному объекту каждого типа (потомок db.Model). К примеру, если у вас есть объекты моделей Album и Track, необходимо выполнить следующее:
if __name__=="__main__":
bulkload.main(AlbumLoader(), TrackLoader())
В коде указаны потомки классов bulkload.Loader — хотя вы можете не описывать свои новые классы, достаточно будет создать экземпляры класса bulkload.Loader и передать ему все параметры в конструктор. Кстати о параметрах, сначала указывается имя объекта, который загружается в приложение, затем список кортежей, каждый из которых содержит имя свойства модели и тип значения (либо стандартный тип языка Python, либо стандартный тип для хранилища, но не тип самого свойства — например, допускается str, но не db.StringProperty()).
Это все, в обычном случае все предельно просто.
Монстроидальный Cookie
Первой недокументированной проблемой, с которой вы столкнетесь, будет безопасность. К примеру, если вы захотите, как это и покажется логичным, ограничить доступ к обработчику только для администраторов приложения, то обнаружите, что не существует способа передать пароль администратора скрипту bulkload_client.py. Вместо этого вам нужно будет указать очень, очень длинный cookie. Как сделать это?
Авторизируйтесь в вашем приложении через свой браузер и перейдите по адресу, к которому привязан обработчик загрузки данных. Затем, выполните запрос GET, вместо используемого POST и увидите cookie, который необходимо будет использовать.
Адский набор символов
Если вы попытаетесь загрузить данные в кодировке UTF-8, то это может привести утилиту из среды разработки SDK в шок. Это происходит потому, что обработчик пытается привести полученные данные из UTF-8 в родной тип unicode еще до того, как передает его загрузчику, но класс BulkLoad.Load считает эти данные все еще закодированными (точнее модуль языка Python, отвечающий за работу с CSV ожидает тип str, а переданный ему тип unicode будет повторно декодировать их при помощи кодека ascii, что и приводит к порче данных).
Надеюсь эту проблему скоро исправят, а пока временно приходится использовать обходное решение: создавать собственный класс, наследник класса bulkload.BulkLoad, который перекодирует данные:
class BulkLoad(bulkload.BulkLoad):
def Load(self, kind, data):
return bulkload.BulkLoad.Load(self, kind, data.encode('utf-8'))
Для определения собственной версии загрузчика, вам необходимо целиком скопировать код функции bulkload.main в свой модуль и отредактировать его — пока я не обнаружил другого удобного способа. Я надеюсь, что эту ошибку исправят довольно скоро.
Винсент Исамбарт указал мне на то, что при инициализации экземпляра класса необходимо упомянуть, что вы используете utf-8. Я определил функцию, которая выполняет это преобразование:
def utf8string(s):
return unicode(s, 'utf-8')
…и затем использовать ее вместо типов str и unicode в конструкторе:
class UserLoader(bulkload.Loader):
def __init__(self):
bulkload.Loader.__init__(self, 'User', [ ('name', utf8string), ])
Загадочный метод HandleEntity
В статье был приведен пример реализации метода HandleEntity, но никаких объяснений, как его можно использовать не было. Это очень полезное дополнение для обработки данных. По существу, он получает данные объекта и может изменить их так, как необходимо разработчику, после чего обратно вернуть его. Есть несколько интересных моментов:
- Передаваемый объект является экземпляром класса datastore.Entity, а не db.Model. По своей сути он является обычным словарем языка Python с дополнительными опциями, может быть использован как обычный словарь dictionary['item'], если имеется необходимость изменения данных перед записью их в хранилище.
- Если в процессе обработки появится необходимость создать дополнительный объект, то вы можете сделать это, передав название модели в качестве строки и опционально указать параметры name (ключ объекта) и его родителя parent (должен быть типа Key.)
- Основной причиной, по которой вы можете создать еще один объект, будет возможность вернуть любое количество новых объектов из функции HandleEntity, таким образом каждая отдельная строка CSV файла может порождать большие массивы данных.
- Пожалуй, наиболее интересной является возможность вообще не создавать объекты. Таким образом Поточный загрузчик превращается в Поточный универсальный обработчик. Если метод не возвращает объектов, то ничего нового в хранилище не создается, и модель данных, переданная функции становится нерелевантной. Вы можете использовать такой загрузчик для выполнения практически любой повторяющейся функции, которая принимает в качестве параметров значения из CSV файла.
К примеру, ниже приведен код Bulk Deleter, который получает список ключей объектов для удаления их из CSV файла:
class BulkDeleter(bulkload.Loader):
def __init__(self):
bulkload.Loader.__init__(self, 'BulkDelete', [ ('key', str), ])
def HandleEntity(self, entity):
key = entity['key'] entity = db.get(db.Key(key))
if entity:
entity.delete()
return []
Другие проблемы
Все это хорошо, но, к сожалению, медленно и ненадежно. Может быть мне не повезло, но я часто видел ошибки 502 Bad Gateway и 500 Server Error, которые прерывали работу загрузчика. Один из параметров, которые можно передать программе bulkload_client.py, позволяет указать количество строк CSV файла, которые единовременно передаются за один раз. Его уменьшение может помочь решить проблему, но это не кажется правильным методом. Каждый раз вы запускаете процесс загрузки данных с самого начала.
Я отправил патч для скрипта, запускаемого на стороне клиента, который выполняет пропуск заданного количества строк от начала файла, таким образом можно просто возобновить прерванную загрузку данных. Он также сообщает, сколько строк с данными было загружено на сервер, до того как произошла ошибка, и таким образом вы всегда будете знать, с какого места нужно возобновить загрузку. Возможно вы захотите, чтобы обработка данных происходила индемпотентно, так как не можете быть всегда уверены, где произойдет ошибка. Поэтому возможно захотите выполнять проверку существования объектов до их добавления к хранилищу, в противном случае придется указывать точное значение их ключей и тогда они будут перезаписывать объекты со старыми данными с помощью метода put().
Надеюсь, этот материал окажется полезным! Хотя… нет, подождите. Я надеюсь что он окажется бесполезным, так как это будет означать что Bulk Loader стал работать как надо.
В любом случае, надеюсь, что ваши данные будут загружены без проблем!
Народ у кого получился метод загрузки в потоке файла с русскими буквами , выложите пожалуйстак готовое решение в архивчике.
Всем спасибо, решение найдено.