Sjuul Janssen
Sjuul Janssen

Reputation: 1802

App Engine nested entity list

In google cloud datastore I have code equivalent to this:

req = datastore.CommitRequest()
req.mode = datastore.CommitRequest.NON_TRANSACTIONAL
foo = req.mutation.insert_auto_id.add()

barListProperty = foo.property.add()
barListValue = []
for i in range(5):
    ent = datastore.Entity()
    a = ent.property.add()
    set_property(a, 'a', 1)
    b = ent.property.add()
    set_property(b, 'b', i)

set_property(barListProperty, 'barlist', barListValue)

key = datastore.Key()
path_element = key.path_element.add()
path_element.kind = 'Foo'

foo.key.CopyFrom(key)
print datastore.commit(req)

Now I wanted to build the same thing in NDB so I wrote this:

class Foo(Expando):
    pass

foo = Foo()
foo.barlist = [Expando(a=1, b=i) for i in range(5)]
foo.put()

But I get the following error:

File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 570, in dispatch
    return method(*args, **kwargs)
  File "/base/data/home/apps/s~detect-analyze-notify-01a/sjuul.373145649709860280/main.py", line 317, in get
    foo.put()
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/model.py", line 3339, in _put
    return self._put_async(**ctx_options).get_result()
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/tasklets.py", line 325, in get_result
    self.check_success()
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/tasklets.py", line 368, in _help_tasklet_along
    value = gen.throw(exc.__class__, exc, tb)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/context.py", line 748, in put
    key = yield self._put_batcher.add(entity, options)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/tasklets.py", line 368, in _help_tasklet_along
    value = gen.throw(exc.__class__, exc, tb)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/context.py", line 280, in _put_tasklet
    keys = yield self._conn.async_put(options, datastore_entities)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/tasklets.py", line 454, in _on_rpc_completion
    result = rpc.get_result()
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/apiproxy_stub_map.py", line 612, in get_result
    return self.__get_result_hook(self)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/datastore/datastore_rpc.py", line 1818, in __put_hook
    self.check_rpc_success(rpc)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/datastore/datastore_rpc.py", line 1333, in check_rpc_success
    raise _ToDatastoreError(err)
BadRequestError: BLOB, ENITY_PROTO or TEXT properties must be in a raw_property field.

How should I go about this?

EDIT: This didn't work either

    class Foo(Expando):
        pass

    class Bar(Expando):
        pass

    foo = Foo()
    foo.barlist=[Bar(a=1, b=i) for i in range(5)]
    foo.put()

Upvotes: 0

Views: 485

Answers (3)

Ananda Rodrigues
Ananda Rodrigues

Reputation: 1

A good option is to set a new entity for the list. SO you can insert as many items as you may need, as instances of the 'list entity', and you can set the other entity as it parent.

Instead of this (or similar):

foo = Foo()
foo.barlist=[Bar(a=1, b=i) for i in range(5)]
foo.put()

you could try this:

foo = ListFoo(parent= Foo)
foo.item = 'list item'
foo.put()

Upvotes: 0

JJ Geewax
JJ Geewax

Reputation: 10579

It looks to me that you're trying to save an entity that itself wasn't persisted yet, which leaves a couple of options:

1) If you want to have the Entity stored as a separate "row", you can save it, and then store a list of keys:

class Entity(db.Expando):
  pass

# Create your main entity.
e = Entity()
e.other_entities = []

# Create a bunch of others.
for i in range(5):
  other_entity = Entity(a=i, b=i+1)
  other_entity.put()
  # Attach the key to the main entity.
  e.other_entities.append(other_entity.key())

# Save your main entity.
e.put()

2) If you want the Entity stored "in-line" you might be able to use the db.EmbeddedEntity type:

class Entity(db.Expando):
  pass

# Create your main entity.
e = Entity()
e.other_entities = []

# Create a bunch of others (but don't save them).
for i in range(5):
  other_entity = Entity(a=i, b=i+1)
  # Append it to the list as an EmbeddedEntity
  e.other_entities.append(db.EmbeddedEntity(other_entity))

# Save your main entity.
e.put()

An example sort of like this is on the main Expando documentation page, where they use db.Text('Text value') to specify that it should be stored as a TextProperty and not a StringProperty.

Upvotes: 0

Tim Hoffman
Tim Hoffman

Reputation: 12986

You can not use the Expando model directly. You will need to create a subclass of ndb.Expando for the repeated property for this to work.

e.g.

s~lightning-catfish> class X(ndb.Expando):
...    pass

s~lightning-catfish> class Repeated(ndb.Expando):
...    pass
... 
s~lightning-catfish> z = X()
s~lightning-catfish> z.y = [Repeated(a=1,b=i) for i in range(5)]
s~lightning-catfish> z.y
[Repeated(a=1, b=0), Repeated(a=1, b=1), Repeated(a=1, b=2), Repeated(a=1, b=3), Repeated(a=1, b=4)]

Upvotes: 1

Related Questions