Subclassing PolyModel

57 views
Skip to first unread message

Nickolas Daskalou

unread,
Mar 3, 2010, 10:16:16 PM3/3/10
to Google App Engine
I've been trying to create a "special" subclass of PolyModel (that does cool stuff like update Memcache after an entity put()) which my polymodel models can inherit from (instead of inheriting from db.polymodel.PolyModel).

This has the nasty side effect though of making that  "special" subclass of PolyModel the root class for my polymodel models.

Is there a way around this?

Eg. Telling db.polymodel.PolyModel to NOT include the first direct subclass in the class hierarchy that is saved to the Datastore? Or writing my own PolyModel by basically copying db.polymodel and changing code where appropriate?

Appreciate any help.

Nick

Nickolas Daskalou

unread,
Mar 3, 2010, 10:40:59 PM3/3/10
to Google App Engine
I should state that I can set the entity kind that's saved in the
Datastore by adding a kind() method on my "real" models, however the
class list property of the saved entities still includes the "special"
PolyModel subclass in it, and I'm not sure if that's a bad thing or
not.

Rafe

unread,
Mar 4, 2010, 4:52:11 PM3/4/10
to Google App Engine
There is currently no proper way to do "re-root" a polymodel the way
you are describing. However, there may be a solution for your
particular problem. I don't know enough about your actual
implementation however you might get away with simply doing a non-
Model based mix-in. So, write your memcache utility class AS IF it
inherits from PolyModel, but don't actually allow it to do so. Like
this:

class CoolFeature(object):

def put(self):
... clear memcache ...
super(CoolFeature, self).put()

Now define your root class:

class MyModel(CoolFeature, polymodel.PolyModel):
...

Here comes the Python lesson, so forgive me if you already know all
this ;)

The key is that you use 'super' correctly and that you have
CoolFeature be the first class your model class inherits from.
'super' allows you to traverse your objects functionality correctly
according to the python "method resolution order". You can see this
order by for any class by studying MyModel.__mro__. This indicates
the order which calls to super will traverse your class definitions.
If you do:

class A(object):

def p(self):
print 'A'


class B(object):

def p(self):
print 'B'
super(B, self).p()


class C(B, A):

def p(self):
print 'C'
super(C, self).p()

...calling C().p() will print:

C
B
A

You should be able to implement whatever functionality you need this
way. Let me know if there are issues with doing that.

Nickolas Daskalou

unread,
Mar 4, 2010, 8:40:24 PM3/4/10
to google-a...@googlegroups.com
Thanks for your reply Rafe. Using non-Model mix-ins seems like the way to go.

As with many App Engine developers, my Python experience is equivalent to my App Engine experience, so I appreciated the Python lesson ;)

Nick


--
You received this message because you are subscribed to the Google Groups "Google App Engine" group.
To post to this group, send email to google-a...@googlegroups.com.
To unsubscribe from this group, send email to google-appengi...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/google-appengine?hl=en.


Nickolas Daskalou

unread,
Mar 5, 2010, 1:22:05 AM3/5/10
to google-a...@googlegroups.com
Using non-Model mix-ins worked a treat. Thanks again Rafe.

There is one small problem I'm hoping you can help me with (again). It's not a show-stopper because I can work around it, however I'm hoping you'd know of a nicer way to do this. Note that it's not only specific to PolyModel, but to db.Model as well.

The problem is with adding extra db.* properties to these mix-ins (so that all subclasses that use the mix-in get these extra properties stored in the Datastore).

Let's say I change the CoolFeature mix-in to this:

class CoolFeature(object):

  cool_name = db.StringProperty()

  def put(self):
    self.cool_name = 'Cool ' + self.name
    super(CoolFeature, self).put()

class MyModel(CoolFeature,db.Model):

  name = db.StringProperty()

If I then create an instance of MyModel and try put()'ing it to the Datastore:

mm = MyModel(name='blah')
mm.put()

I get this error:

Traceback (most recent call last):
  ...
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/db/__init__.py", line 473, in __set__
    setattr(model_instance, self._attr_name(), value)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/db/__init__.py", line 588, in _attr_name
    return '_' + self.name
TypeError: cannot concatenate 'str' and 'NoneType' objects

This error occurs both on the dev and production servers.

Upon further investigation, the error occurs because the name property of the underlying Property superclass of the cool_name property instance is None. So I then changed the bold line above to:

  cool_name = db.StringProperty(name='cool_name')

This got rid of the error being raised, however the cool_name property was not saved to the Datastore (only the name property was).

The workaround is to add the property cool_name to each CoolFeature subclass. So it's not terribly bad, but if you know of a better way to accomplish what I'm trying to do, I'd love to hear it.

Cheers,

Nick

PK

unread,
Mar 5, 2010, 3:41:16 AM3/5/10
to Google App Engine
Hi Nick,

I have been using the pattern described by Rafe very successfully in
my app. Regarding the problem you are desribing I have written up how
I solved the problem here:

http://www.gae123.com/articles/tds/model-mixin.html

Read the whole page for the details or just scroll to the bottom for
the solution that has worked for me.

PK
http://www.gae123.com

On Mar 4, 10:22 pm, Nickolas Daskalou <n...@daskalou.com> wrote:
> Using non-Model mix-ins worked a treat. Thanks again Rafe.
>
> There is one small problem I'm hoping you can help me with (again). It's not
> a show-stopper because I can work around it, however I'm hoping you'd know
> of a nicer way to do this. Note that it's not only specific to PolyModel,
> but to db.Model as well.
>
> The problem is with adding extra db.* properties to these mix-ins (so that
> all subclasses that use the mix-in get these extra properties stored in the
> Datastore).
>
> Let's say I change the CoolFeature mix-in to this:
>
> class CoolFeature(object):
>

> *  cool_name = db.StringProperty()*


>
>   def put(self):
>     self.cool_name = 'Cool ' + self.name
>     super(CoolFeature, self).put()
>
> class MyModel(CoolFeature,db.Model):
>
>   name = db.StringProperty()
>
> If I then create an instance of MyModel and try put()'ing it to the
> Datastore:
>
> mm = MyModel(name='blah')
> mm.put()
>
> I get this error:
>
> Traceback (most recent call last):
>   ...
>   File

> "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngi­ne-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/­db/__init__.py",


> line 473, in __set__
>     setattr(model_instance, self._attr_name(), value)
>   File

> "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngi­ne-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/­db/__init__.py",


> line 588, in _attr_name
>     return '_' + self.name
> TypeError: cannot concatenate 'str' and 'NoneType' objects
>
> This error occurs both on the dev and production servers.
>
> Upon further investigation, the error occurs because the name property of
> the underlying Property superclass of the cool_name property instance is
> None. So I then changed the bold line above to:
>
>   cool_name = db.StringProperty(name='cool_name')
>
> This got rid of the error being raised, however the cool_name property was
> not saved to the Datastore (only the name property was).
>
> The workaround is to add the property cool_name to each

> CoolFeaturesubclass. So it's not terribly bad, but if you know of a


> better way to
> accomplish what I'm trying to do, I'd love to hear it.
>
> Cheers,
>
> Nick
>

> On 5 March 2010 12:40, Nickolas Daskalou <n...@daskalou.com> wrote:
>
>
>
> > Thanks for your reply Rafe. Using non-Model mix-ins seems like the way to
> > go.
>
> > As with many App Engine developers, my Python experience is equivalent to
> > my App Engine experience, so I appreciated the Python lesson ;)
>
> > Nick
>

> >> google-appengi...@googlegroups.com<google-appengine%2Bunsubscrib­e...@googlegroups.com>


> >> .
> >> For more options, visit this group at

> >>http://groups.google.com/group/google-appengine?hl=en.- Hide quoted text -
>
> - Show quoted text -

Nickolas Daskalou

unread,
Mar 5, 2010, 4:18:28 AM3/5/10
to google-a...@googlegroups.com
Thanks PK, I'll check it out now.

Nick


To unsubscribe from this group, send email to google-appengi...@googlegroups.com.

Nickolas Daskalou

unread,
Mar 5, 2010, 4:31:40 AM3/5/10
to google-a...@googlegroups.com
Cool@your solution, PK!

Since db.PropertiedClass is not documented in the official docs, I'm assuming there's a chance that its implemented could change in future releases, and possibly break your code yeah?

Can someone from Google please comment on whether db.PropertiedClass is "concrete" enough to be able to use it in such a manner as described in PK's article?

Nick

Rafe

unread,
Mar 10, 2010, 6:01:37 PM3/10/10
to Google App Engine
The built-in property classes are not likely to change in future
releases. I've already written an article about extending properties
and am loath to break peoples existing applications (even though it
sometimes happens by mistake - and we then revert those changes):

http://code.google.com/appengine/articles/extending_models.html

Nice analysis PK.

As described, the main problem with your first approach was that
properties are not initialized unless they use the Propertied base
class. The problem with the Propertied base class is that it is too
tied to the actual Datastore implementation. Ideally, there would be
a less specific meta-class that simply does property initialization
which can be used by other class that do not inherit from models but
want to be mixins. This would be ideal:

class CoolFeature(db.ModelMixin):
my_prop = db.StringProperty()

class IUseCoolFeature(CoolFeature, db.Model): ...

Or what if you wanted to use it with an Expando?

class IUseCoolFeature(CoolFeature, db.Expando): ...

Learned quite a bit since releasing db.py. Expando should have been
written as a mixin in the first place.

On Mar 5, 1:31 am, Nickolas Daskalou <n...@daskalou.com> wrote:
> Cool@your solution, PK!
>
> Since db.PropertiedClass is not documented in the official docs, I'm
> assuming there's a chance that its implemented could change in future
> releases, and possibly break your code yeah?
>
> Can someone from Google please comment on whether db.PropertiedClass is
> "concrete" enough to be able to use it in such a manner as described in PK's
> article?
>
> Nick
>

> >> "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngi ­ne-default.bundle/Contents/Resources/google_appengine/google/appengine/ext /­db/__init__.py",


> >> > line 473, in __set__
> >> >     setattr(model_instance, self._attr_name(), value)
> >> >   File
>

> >> "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngi ­ne-default.bundle/Contents/Resources/google_appengine/google/appengine/ext /­db/__init__.py",

> >> > >>http://groups.google.com/group/google-appengine?hl=en.-Hide quoted


> >> text -
>
> >> > - Show quoted text -
>
> >> --
> >> You received this message because you are subscribed to the Google Groups
> >> "Google App Engine" group.
> >> To post to this group, send email to google-a...@googlegroups.com.
> >> To unsubscribe from this group, send email to

Nickolas Daskalou

unread,
Mar 10, 2010, 10:51:15 PM3/10/10
to google-a...@googlegroups.com
Rafe, do you think we'll be seeing a db.ModelMixin anytime in the near future?


To unsubscribe from this group, send email to google-appengi...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages