Change Class of Polymodel Instance

191 views
Skip to first unread message

PatrickCD

unread,
Sep 27, 2011, 10:42:41 AM9/27/11
to Google App Engine
Hello,

Is it safe to change the __class__ attribute of a PolyModel instance
and then save it back to the datastore?

e.g.

class DraftProject(PolyModel):
def someaction(self):
print 'Action permitted at this status'

class ApprovedProject(DraftProject):
def someaction(self):
raise Exception('not allowed at this status')

p = DraftProject.get(object_key)
p.__class__ = ApprovedProject
p.put()


I've a got a model class that corresponds to a project in a workflow
application. The project can be at different statuses. Depending on
the status, some actions are permissible, and some are not. I'd like
to use the Template design pattern to model this (http://
en.wikipedia.org/wiki/Template_method_pattern)

If setting the class attribute directly is unsafe, could anyone
suggest a better way to model this?

Thanks

Tim Hoffman

unread,
Sep 27, 2011, 7:35:54 PM9/27/11
to google-a...@googlegroups.com
Hi

You wouldn't do it that way, and it won'tt work (well it might if you really want to go way under the hood)

The "class" property of the PolyModel stores an ordered list of parent classes.  The base class that will be looked up on retrieval from the datastore is defined in the Key, and in your case will always be "DraftProject". When the entity is retrieved from the Datastore the
Key's path is used to resolve the base class. Then metaclasses kick in and it actually returns an instance of the subclass
You can't change the key of an entity.

Have a look at polymodel based entities in the datastore. You will see that the "class" property stores a list of Class names.


However I have yet to work out a way to change the class, manipulating the "class" property doesn't work ;-)
If you go under the hood and change the "class" property in a raw entity it would work though.  But this is getting seriously messy.

I would seriously reconsider if this is a path you want to take.  Why not consider using adapters and have a simple class
that stores the entities properties (workflow) status and then wrap the entity with an adapter with the methods you want etc... based on the status.

I feel this would be a better approach.  I use adapters (zope.component) on appengine in pretty much all of my projects. It would
give you the same behaviour r without having to hack around with the PolyModel metaclass machinery.

Regards

Tim Hoffman

PatrickCD

unread,
Sep 30, 2011, 9:27:08 AM9/30/11
to Google App Engine
Tim,

Thanks for that informative and useful answer. I was a apprehensive
messing with the datastore class machinery. The approach I adopted was
(directly setting __class__ attribute) appears to work. However, as
you suggest, it seems likely this behaviour is not something to rely
on. I agree that using some sort or adapter / factory method is
probably better. Thanks again.

Regards

Patrick

Gwyn Howell

unread,
Sep 30, 2011, 10:25:36 AM9/30/11
to google-a...@googlegroups.com
why not just build a status field into your class, then use properties to check the status before setting/getting data or calling methods? much cleaner than changing the class

PatrickCD

unread,
Oct 2, 2011, 5:25:49 PM10/2/11
to Google App Engine
I agree that using a status field is initially more simple, and that
simple is good. However, consider a situation where you have many
methods in a class. The behaviour of these methods needs to change
depending on the status, e.g. some methods should raise exceptions
for for some users at some statuses. What tends to happen is that you
end up with lots of if/and/or statements littered throughout your
code. That's not clean at all. Using class inheritance mapped to
status (Template pattern) can result in code that is more maintainable
and less buggy.

Murph

unread,
Oct 4, 2011, 10:14:51 AM10/4/11
to google-a...@googlegroups.com
Here's a method I've discovered for changing the class on PolyModel, which I think is relatively clean:

ent1 = Model1.get(…)  # Or get_by_id, etc
dict = db.to_dict(ent1)
del dict['class']
# Other changes to dict, as required, adding/removing properties
ent2 = Model2(key=ent1.key(), **dict)
ent2.put()

I think that's fairly clean, although I've not used it heavily.

Nick Johnson

unread,
Oct 4, 2011, 10:50:00 PM10/4/11
to google-a...@googlegroups.com
Hi Patrick,

It's never 'safe' to mess with the internals of Python like this. As a rule, if you're trying to change the class of an existing object, that's a code smell, and you should almost certainly be implementing it differently, such as by using a single class and changing a status variable.

-Nick Johnson


--
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.




--
Nick Johnson, Developer Programs Engineer, App Engine


Murph

unread,
Oct 4, 2011, 11:09:41 PM10/4/11
to google-a...@googlegroups.com
Nick, any comment on my approach of changing the PolyModel's class via db.to_dict and creating a new model with the same key?  That seems to me like a relatively clean & safe way to do it.  The desire to be able to do this for my case, is to be able to move records from Person to User, then User to SuperUser, gaining extra fields needed as the person descends the hierarchy.  I suspect that's a fairly common scenario, and seems almost exactly the type of thing that PolyModel was designed for.

Nick Johnson

unread,
Oct 4, 2011, 11:34:01 PM10/4/11
to google-a...@googlegroups.com
On Wed, Oct 5, 2011 at 2:09 PM, Murph <paul.j...@googlemail.com> wrote:
Nick, any comment on my approach of changing the PolyModel's class via db.to_dict and creating a new model with the same key?  That seems to me like a relatively clean & safe way to do it.  The desire to be able to do this for my case, is to be able to move records from Person to User, then User to SuperUser, gaining extra fields needed as the person descends the hierarchy.  I suspect that's a fairly common scenario, and seems almost exactly the type of thing that PolyModel was designed for.

In that case you're not changing the class of an object, just creating a new object with existing data, which is fine. It's still a very strange thing to be doing, though, and you should just have the full set of fields on the basic 'User' record, not using them where they're superfluous.

PolyModel is designed for the same sort of modelling situations that regular OO is designed for. All Cars have LicenseNumbers, but different types of car may have different properties - and a Porshe will never turn into an Audi.

-Nick Johnson
 

--
You received this message because you are subscribed to the Google Groups "Google App Engine" group.
To view this discussion on the web visit https://groups.google.com/d/msg/google-appengine/-/O3DQBp7xtBAJ.

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.

Tim Hoffman

unread,
Oct 5, 2011, 1:08:27 AM10/5/11
to google-a...@googlegroups.com
I agree

I actually think a Expando model might be more appropriate combined with adapters.

That way you can store additional information if required due to different requirements, 
but keep the specific state based methods in the adapters, if you don't want to start
aggregating methods from different states.

However I would seriously look at your models to see if you not in a situation where you 
have a "pattern" you want to use, which doesn't really suit the environment in which your
developing. Often patterns described suit the restrictions of specific languages like
Java, and python has it's own approaches.

See ya

Tim 


PatrickCD

unread,
Oct 5, 2011, 8:25:11 AM10/5/11
to Google App Engine
Nick,

> It's never 'safe' to mess with the internals of Python like this. As a rule,
> if you're trying to change the class of an existing object, that's a code
> smell, and you should almost certainly be implementing it differently, such

Hi. As a rule, when someone uses the term "code smell", that's a
"cliche smell".

Google bundles django with AppEngine, and django messes with the
internals of Python like crazy, so Google hopefully does think it's
safe in some cases.

I want to put some data into the datastore. Depending on the value of
that data, I want it to map to a different class when I get it back.
Changing the __class__ attribute currently works. I posted this
question for two reasons:

- to find out whether or not this approach was likely to break in
some cases or with future versions of DataStore API.
- because changing __class__ feels hackish and I was hoping someone
might suggest a better approach

Nick Johnson

unread,
Oct 5, 2011, 8:42:44 AM10/5/11
to google-a...@googlegroups.com
On Wed, Oct 5, 2011 at 11:25 PM, PatrickCD <patric...@gmail.com> wrote:
Nick,

> It's never 'safe' to mess with the internals of Python like this. As a rule,
> if you're trying to change the class of an existing object, that's a code
> smell, and you should almost certainly be implementing it differently, such

Hi. As a rule, when someone uses the term "code smell", that's a
"cliche smell".

Feel free to reword it any way you wish. Trying to change the class of an object usually means you're trying to do something that OO wasn't designed to do. Just because Python makes it possible doesn't mean you should do it.
 

Google bundles django with AppEngine, and django messes with the
internals of Python like crazy, so Google hopefully does think it's
safe in some cases.

Bundling doesn't signify endorsement. Also, I'm pretty sure Django doesn't do anything like modifying a class instance's parents after  it's instantiated.


I want to put some data into the datastore. Depending on the value of
that data, I want it to map to a different class when I get it back.
Changing the __class__ attribute currently works. I posted this
question for two reasons:

 - to find out whether or not this approach was likely to break in
some cases or with future versions of DataStore API.

Absolutely no guarantees on this front. You're breaking any number of invariants by doing this, so things may break without notice at any time.
 
 - because changing __class__ feels hackish and I was hoping someone
might suggest a better approach

It _is_ a hack. A better approach would be to use a single class with appropriate properties, or create a new instance of a new class and discard the old one at such time as you need to make the change.

-Nick Johnson
 

--
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.

PatrickCD

unread,
Oct 5, 2011, 9:08:52 AM10/5/11
to Google App Engine
Tim,

> I actually think a Expando model might be more appropriate combined with
> adapters.

Hi. As far as I understand it, Expando models allow you to store
additional data, but I'm looking to change behaviour, not state.

> have a "pattern" you want to use, which doesn't really suit the environment
> in which your
> developing. Often patterns described suit the restrictions of specific
> languages like
> Java, and python has it's own approaches.

I realise that using the term "Pattern" with python developers can
evoke nightmares of being kidnapped by Java architects and forced to
develop Enterprise Java Beans whilst Larry Ellison tortures their
pets.

I'll try to rephrase the problem without reference to any evil Gang
of Four literature.

You've got a class with 10 attributes and 10 methods. The behaviour of
all of these methods changes radically depending on the value of one
of the attributes (e.g. 'status', which has one of 3 possible values.
What's the best way to model this? Inheritance seems a very elegant
approach to me. See below for a facetious example. Once you expand
this to 30 significant methods the benefits of this approach are very
apparent.

class Developer():

experience = StringField()

def react_to_question(self):

if self.experience == 'Novice':
self.wtf?()
if self.experience == 'Recent Graduate':
self.patronise_questionner()
if self.experience == 'Disillusioned':
self.wonder_why_not_a_fireman()
self.reach_for_bottle()
# Alternative

class BaseDeveloper():
pass

class Novice(BaseDeveloper):
def react_to_question(self):
self.wtf?()

class Graduate(BaseDeveloper):
def react_to_question(self):
self.patronize_questionner()

class Disillusioned(BaseDeveloper):
def react_to_question(self):
self.wonder_why_not_a_fireman()
self.reach_for_bottle()

PatrickCD

unread,
Oct 5, 2011, 10:34:14 AM10/5/11
to Google App Engine
Nick,

> It _is_ a hack. A better approach would be to use a single class with
> appropriate properties, or create a new instance of a new class and discard
> the old one at such time as you need to make the change.

Thanks for your reply. I'm sure you're bored of this thread so I'll
try to summarise.

To take another example, SQLAlchemy supports what it calls "Single
Table Inheritance" - http://www.sqlalchemy.org/docs/orm/inheritance.html#single-table-inheritance
. In this approach, there is a field "type" that SQLAlchemy uses to
determine what class the row should be mapped to. So if you change the
'type' you change the class.

You seem to be saying that each record in the datastore should map to
one and only one Python class, so a similar approach doesn't work.

Polymodel Objects in the GAE Datastore have a property "class" which
stores a list of super classes. This property appears to be used by
the DataStore API in a similar way to how "type" is used by
SQLAlchemy. It's currently possible to change the value of this field
by setting the __class__ attribute of an instance and saving it back
to the DataStore. However, you state:

"You're breaking any number of invariants by doing this, so things may
break without notice at any time. "

Somewhat cryptic, but I'll take your word for it.

So perhaps the question boils down to whether or not there _is_ a
reliable way to change the value of the class property for polymodel
records? I'm guessing the answer is "no", but I'd like to check.

Thanks

Patrick

Nick Johnson

unread,
Oct 5, 2011, 7:18:57 PM10/5/11
to google-a...@googlegroups.com
On Thu, Oct 6, 2011 at 1:34 AM, PatrickCD <patric...@gmail.com> wrote:
Nick,

> It _is_ a hack. A better approach would be to use a single class with
> appropriate properties, or create a new instance of a new class and discard
> the old one at such time as you need to make the change.

Thanks for your reply. I'm sure you're bored of this thread so I'll
try to summarise.

To take another example, SQLAlchemy supports what it calls "Single
Table Inheritance" - http://www.sqlalchemy.org/docs/orm/inheritance.html#single-table-inheritance
. In this approach, there is a field "type" that SQLAlchemy uses to
determine what class the row should be mapped to. So if you change the
'type' you change the class.

This is true, but I think if you ask the SQLAlchemy authors, they would also say that they don't expect this to change after a row is created.
 

You seem to be saying that each record in the datastore should map to
one and only one Python class, so a similar approach doesn't work.

No, you can map datastore entities to objects any way you like. I'm just pointing out that the thing you're trying to do is a hack, and violates some basic expectations OO systems make, and the way you're trying to do it is unsupported and may break.

 

Polymodel Objects in the GAE Datastore have a property "class" which
stores a list of super classes. This property appears to be used by
the DataStore API in a similar way to how "type" is used  by
SQLAlchemy.  It's currently possible to change the value of this field
by setting the __class__ attribute of an instance and saving it back
to the DataStore. However, you state:

"You're breaking any number of invariants by doing this, so things may
break without notice at any time. "

Somewhat cryptic, but I'll take your word for it.

Invariants like "the set of superclasses of an instance will remain constant after an object is created". It's possible that not just us, but also other Python libraries expect that invariant, so once you start messing with it, you may get unexpected problems elsewhere.
 

So perhaps the question boils down to whether or not there _is_ a
reliable way to change the value of the class property for polymodel
records? I'm guessing the answer is "no", but I'd like to check.

You could use the low level datastore API to modify the class property in the datastore.

If I understand correctly, you want the behavior of a class's methods to depend on the 'type' of entity being handled, and that 'type' may change over the lifetime of an entity. Let me suggest an alternative way to do what you're trying to do:

- Create a "DeveloperBehavior" base class, which encapsulates the behaviors that change between types
- Create subclasses for each type of behavior.
- Create a 'behaviour' property on your model class, which fetches or instantiates the appropriate behavior for the current entity.
- Either call the behavior methods directly, or wrap them with methods on your entity.

This will allow you to change the behavior at runtime, without messing with Python and DB internals.

-Nick Johnson


 

Thanks

Patrick


--
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.

Tim Hoffman

unread,
Oct 5, 2011, 7:37:12 PM10/5/11
to google-a...@googlegroups.com


On Wednesday, October 5, 2011 9:08:52 PM UTC+8, PatrickCD wrote:
Tim,

> I actually think a Expando model might be more appropriate combined with
> adapters.

Hi. As far as I understand it, Expando models allow you to store
additional data, but I'm looking to change behaviour, not state.



I was referring to using adapters with an Expando class.

Bottom line if you are comfortable and capable messing with internals, then go right ahead, 
however the fact you even asking suggests you probably aren't therefore you probably shouldn't

PatrickCD

unread,
Oct 10, 2011, 4:41:59 PM10/10/11
to Google App Engine
Sounds like a good plan. Thanks for your thoughts.

On Oct 6, 12:18 am, Nick Johnson <nickjohn...@google.com> wrote:
> On Thu, Oct 6, 2011 at 1:34 AM, PatrickCD <patrick.do...@gmail.com> wrote:
> > Nick,
>
> > > It _is_ a hack. A better approach would be to use a single class with
> > > appropriate properties, or create a new instance of a new class and
> > discard
> > > the old one at such time as you need to make the change.
>
> > Thanks for your reply. I'm sure you're bored of this thread so I'll
> > try to summarise.
>
> > To take another example, SQLAlchemy supports what it calls "Single
> > Table Inheritance" -
> >http://www.sqlalchemy.org/docs/orm/inheritance.html#single-table-inhe...
Reply all
Reply to author
Forward
0 new messages