Подтвердить что ты не робот

Скопируйте объект в хранилище данных Google App Engine в Python, не зная имена свойств во время компиляции

В приложении 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 (все свойства имеют значение по умолчанию), но совсем не то, что я хочу. Предложения?

4b9b3361

Ответ 1

Здесь вы идете:

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())

EDIT: изменяется, если используется NDB

Объединив комментарий Gus ниже с исправлением для свойств, которые указывают другое имя хранилища данных, для 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'.

Ответ 2

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

Ответ 3

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

  • Значение db.Key для ReferenceProperty больше не извлекается через ненужный обратный путь к хранилищу данных.
  • Теперь вы можете указать, хотите ли вы пропускать свойства 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 не очень элегантно, поэтому я ценю, если вы можете поделиться лучшим способом его записи.

Ответ 4

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

props = {}
for p in Thing.properties():
    props[p] = getattr(old_thing, p)
new_thing = Thing(**props).put()

Ответ 5

Вариант, вдохновленный ответом Ника, который обрабатывает случай, когда ваш объект имеет (повторяющийся) 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)

Ответ 6

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

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

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())... но если вы модели достаточно просты, и вам нравится жить на краю:)

Ответ 7

Здесь код, предоставленный @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)