Скопируйте объект в хранилище данных Google App Engine в Python, не зная имен свойств во время «компиляции» - PullRequest
39 голосов
/ 22 апреля 2010

В приложении Python для Google App Engine, которое я пишу, в хранилище данных хранится сущность, которую мне нужно извлечь, сделать ее точную копию (за исключением ключа), а затем вернуть эту сущность обратно в.

Как мне это сделать? В частности, есть ли какие-то предостережения или уловки, о которых мне нужно знать, когда я делаю это, чтобы я получил ожидаемую копию, а не что-то еще.

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

#theThing = a particular entity we pull from the datastore with model Thing
copyThing = Thing(user = user)
for thingProperty in theThing.properties():
    copyThing.__setattr__(thingProperty[0], thingProperty[1])

Это выполняется без каких-либо ошибок ... пока я не попытаюсь извлечь copyThing из хранилища данных, и в этот момент я обнаруживаю, что все свойства установлены в None (за исключением пользователя и ключа, очевидно). Очевидно, что этот код что-то делает, так как он заменяет значения по умолчанию на None (все свойства имеют значение по умолчанию), но не совсем то, что я хочу. Предложения?

Ответы [ 7 ]

59 голосов
/ 26 апреля 2010

Вот, пожалуйста:

def clone_entity(e, **extra_args):
  """Clones an entity, adding or overriding constructor attributes.

  The cloned entity will have exactly the same property values as the original
  entity, except where overridden. By default it will have no parent entity or
  key name, unless supplied.

  Args:
    e: The entity to clone
    extra_args: Keyword arguments to override from the cloned entity and pass
      to the constructor.
  Returns:
    A cloned, possibly modified, copy of entity e.
  """
  klass = e.__class__
  props = dict((k, v.__get__(e, klass)) for k, v in klass.properties().iteritems())
  props.update(extra_args)
  return klass(**props)

Пример использования:

b = clone_entity(a)
c = clone_entity(a, key_name='foo')
d = clone_entity(a, parent=a.key().parent())

РЕДАКТИРОВАТЬ: Изменения при использовании NDB

Комбинируя приведенный ниже комментарий Гаса с исправлением свойств, которые задают другое имя хранилища данных, следующий код работает для NDB:

def clone_entity(e, **extra_args):
  klass = e.__class__
  props = dict((v._code_name, v.__get__(e, klass)) for v in klass._properties.itervalues() if type(v) is not ndb.ComputedProperty)
  props.update(extra_args)
  return klass(**props)

Пример использования (примечание key_name становится id в NDB):

b = clone_entity(a, id='new_id_here')

Примечание: посмотрите, как использовать _code_name, чтобы получить понятное для Python имя свойства. Без этого свойство типа name = ndb.StringProperty('n') заставило бы конструктор модели вызвать AttributeError: type object 'foo' has no attribute 'n'.

19 голосов
/ 30 марта 2012

Если вы используете NDB, вы можете просто скопировать с помощью: new_entity.populate(**old_entity.to_dict())

15 голосов
/ 23 сентября 2011

Это просто расширение превосходного кода Ника Джонсона для решения проблем, отмеченных Амиром в комментариях:

  1. Значение db.Key в ReferenceProperty больше не используетсяизвлекается из ненужного туда-обратно в хранилище данных.
  2. Теперь вы можете указать, хотите ли вы пропустить свойства DateTime с флагом auto_now и / или auto_now_add.

Вотобновленный код:

def clone_entity(e, skip_auto_now=False, skip_auto_now_add=False, **extra_args):
  """Clones an entity, adding or overriding constructor attributes.

  The cloned entity will have exactly the same property values as the original
  entity, except where overridden. By default it will have no parent entity or
  key name, unless supplied.

  Args:
    e: The entity to clone
    skip_auto_now: If True then all DateTimeProperty propertes will be skipped which have the 'auto_now' flag set to True
    skip_auto_now_add: If True then all DateTimeProperty propertes will be skipped which have the 'auto_now_add' flag set to True
    extra_args: Keyword arguments to override from the cloned entity and pass
      to the constructor.
  Returns:
    A cloned, possibly modified, copy of entity e.
  """

  klass = e.__class__
  props = {}
  for k, v in klass.properties().iteritems():
    if not (type(v) == db.DateTimeProperty and ((skip_auto_now and getattr(v, 'auto_now')) or (skip_auto_now_add and getattr(v, 'auto_now_add')))):
      if type(v) == db.ReferenceProperty:
        value = getattr(klass, k).get_value_for_datastore(e)
      else:
        value = v.__get__(e, klass)
      props[k] = value
  props.update(extra_args)
  return klass(**props)

Первое выражение if не очень элегантно, поэтому я ценю, если вы можете поделиться более лучшим способом написать его.

1 голос
/ 21 августа 2015

Вариант, вдохновленный ответом Ника, который обрабатывает случай, когда у вашей сущности есть (повторное) StructuredProperty, где само StructuredProperty имеет ComputedProperties. Скорее всего, это может быть написано более кратко, с пониманием dict, но вот более длинная версия, которая работала для меня:

def removeComputedProps(klass,oldDicc):
  dicc = {}
  for key,propertType in klass._properties.iteritems():
      if type(propertType) is ndb.StructuredProperty:
          purged = []
          for item in oldDicc[key]:
              purged.append(removeComputedProps(propertType._modelclass,item))
          dicc[key]=purged
      else:
          if type(propertType) is not ndb.ComputedProperty:
              dicc[key] = oldDicc[key]
  return dicc

def cloneEntity(entity):
  oldDicc = entity.to_dict() 
  klass = entity.__class__
  dicc = removeComputedProps(klass,oldDicc)
  return klass(**dicc)
1 голос
/ 24 апреля 2010

Я не гуру ни Python, ни AppEngine, но нельзя ли динамически получить / установить свойства?

props = {}
for p in Thing.properties():
    props[p] = getattr(old_thing, p)
new_thing = Thing(**props).put()
0 голосов
/ 08 июня 2017

Вот код , предоставленный @zengabor с выражением if, отформатированным для удобства чтения. Может не соответствовать PEP-8:

klass = e.__class__
props = {}
for k, v in klass.properties().iteritems():
    if not (type(v) == db.DateTimeProperty and ((
            skip_auto_now     and getattr(v, 'auto_now'    )) or (
            skip_auto_now_add and getattr(v, 'auto_now_add')))):
        if type(v) == db.ReferenceProperty:
            value = getattr(klass, k).get_value_for_datastore(e)
        else:
            value = v.__get__(e, klass)
        props[k] = value
props.update(extra_args)
return klass(**props)
0 голосов
/ 02 мая 2014

Это может быть сложно, если вы переименовали основные ключи для своих свойств ... что некоторые люди предпочитают делать вместо массовых изменений данных

скажем, вы начали с этого:

class Person(ndb.Model):
   fname = ndb.StringProperty()
   lname = ndb.StringProperty()

тогда однажды вы действительно решили, что было бы лучше использовать first_name и last_name ... так что вы делаете это:

class Person(ndb.Model):
   first_name = ndb.StringProperty(name="fname")
   last_name = ndb.StringProperty(name="lname")

теперь, когда вы выполняете Person._properties (или .properties () или person_instance._properties), вы получите словарь с ключами, которые соответствуют базовым именам (fname и lname) ... но не будут соответствовать фактическим именам свойств в классе ... так что это не сработает, если вы поместите их в конструктор нового экземпляра или используете метод .populate () (вышеприведенные примеры сломаются)

В любом случае, в NDB экземпляры моделей имеют ._ values ​​ словарь, который основан на именах базовых свойств ... и вы можете обновить его напрямую. Я закончил что-то вроде этого:

    def clone(entity, **extra_args):
        klass = entity.__class__
        clone = klass(**extra_args)
        original_values = dict((k,v) for k,v in entity._values.iteritems() if k not in clone._values)
        clone._values.update(original_values)
        return clone

Это на самом деле не самый безопасный способ ... поскольку есть другие частные вспомогательные методы, которые выполняют больше работы (например, проверка и преобразование вычисляемых свойств с использованием _store_value () и _retrieve_value ( ) ) ... но если вы модели достаточно просты, и вам нравится жить на грани :))

...