views:

32

answers:

1

Are there examples of how to pass a list of key_names to Model.get_or_insert() ?

My Problem:

With a method of ParentLayer I want to make the children.

The key_names of the new (or editable) entities of class Child will come from such a list below:

namesList = ["picture1","picture2"]

so I should be able to build a list of key_names with method from the parent class as follows:

class ParentLayer(db.Model):

 def getOrMakeChildren(self, namesList):
            keyslist = [ db.Key.from_path( 'Child' , name , parent = self.key() ) for name in namesList ]

the problem is next where I simply want to get_or_insert entities based on keylist defined above:

            childrenEntitiesList = Child.get_or_insert(keyslist) # no works?

also none of the below attempts worked:

            #childrenEntitiesList = Child.get_or_insert(keyslist, parent = u'TEST') 
            #childrenEntitiesList = Child.get_or_insert(keyslist, parent=self.key().name() ) 
            #childrenEntitiesList = Child.get_or_insert(keyslist, parent=self.key() 
+2  A: 

get_or_insert has to use a transaction in order to atomically return or create the requested entity, and you can't execute a single transaction over multiple entities. Also, get_or_insert takes keyword arguments for the constructor, and there's no easy way to specify a different set for each.

If you just want to get_or_insert multiple keys, you could do this:

entities = [Child.get_or_insert(k) for k in keylist]

As mentioned, this will require a transaction per entity. If you expect that the entities in question will usually exist, an alternative version of get_or_insert may be more useful (and efficient) for you:

def _get_or_insert_tx(key, properties):
  """When run in a transaction, fetches or creates an entity atomically."""
  entity = db.get(key)
  if not entity:
    entity = db.class_for_kind(key.kind())(key=key, *properties)
    entity.put()
  return entity

def get_or_insert_multiple(args):
  """Fetches or creates multiple entities.

  Args:
    args: A list of (key, dict-of-properties) tuples
  Returns:
    A list of entities, in the same order as args.
  """
  # First, try and fetch them in a batch, outside the transaction
  entities = db.get([x[0] for x in args])
  # Now, transactionally create or fetch each missing one
  for i in range(len(entities)):
    if entities[i] is None:
      entities[i] = db.run_in_transaction(_get_or_insert_tx, *args[i])
  return entities

This code will first attempt to do a batch fetch of all the entities you want to create, and then execute a transaction only for the entities that don't already exist. In the best case, it does a single batch get; in the worst case, it does a batch get followed by a transaction for each entity. Note that, unlike the built in get_or_insert, this one takes keys rather than key names - since that's the syntax you were using.

Here it is in use:

entities = get_or_insert_multiple([(k, {}) for k in keys])

Note the empty dictionary for each entity, since you're not specifying any properties to be passed to the constructor for new entities.

Nick Johnson
OK this looks like it will be very helpful . thank you
indiehacker