On Oct 29, 2007, at 9:41 PM, Ron wrote:
>
> Yes the metaclass makes a mapper for for each subclass. The Thing
> class gets mapped to the thing table while the subclasses get mapped
> to a select() based on their meta_attrs. I used the assign_mapper so
> that someone could just do a Server.select() and get all Things that
> could be managed by that driver. If all the classes ultimately mapped
> to the same table, what are the implications of using the wrong
> mapper? I didn't run into any behavior that wasn't what I desired.
you could just be using one mapper for all the classes here. its
almost like you should monkeypatch class_mapper() and object_mapper()
and just be done with it.
of course the reason mappers are usually specific to a class is
because, every class would have completely different attributes and
relations. but it seems here that is not the case.
>
> So, originally, I did have a 'type' column and chose my class based on
> that, but I wanted something more flexible. So the use case I'm
> trying to cover is if say, my v0.1 code has a Server() class. People
> use it happily and do things like:
>
> s1 = Server('server1')
> s1.addAttr('model', 'sun')
> flush()
>
> s2 = Server('server2')
> s2.addAttr('model', 'apple')
> flush()
>
> etc...
>
> So v0.1 users fill their DBs with all sorts of data. In v0.2 I want
> to add an AppleServer class that subclasses Server. It will add
> killer features like AppleServer.beShiny() and
> AppleServer.commandN().
>
> In the scheme with a type column only _new_ instances of AppleServer
> would have the correct 'appleserver' value in the type column. But I
> want AppleServer to work with _any_ server that also has a ('model',
> 'apple') attribute. So I would have to do an update to the database
> and alter the values throughout the DB. In a way you're also making
> anything with type=='appleserver' NOT a Server because all Servers
> have type=='server'. So in terms of object orientation an AppleServer
> is a Server, but in terms of the datastore an 'appleserver' is not a
> 'server'. (though that's possibly merely an academic quibble).
>
so, you want people to say:
s = Server(model='apple')
then later, they ...upgrade ? by v0.1 -> v0.2 you mean a new copy of
your framework ? or just hypothetical versions of the user's
application ?
then they say:
s = some_query.get_my_thing(criterion)
and they get back an AppleServer, which is some kind of improvement
over Server.
would they ever get a Server back again ? if not, why does the
database need to change ? why not just map AppleServer to
"server" ? also, arent you concerned about query overhead here ?
with all your objects being completely homogenized into a vertical
structure and all, that is. theres no straightforward way for me to
get a list of all the AppleServers, for example, since id have to
query all these different attributes just to identify those objects.
>
> I'm still soaking in these examples. I think what I really want is to
> have mapper accept something like polymorphic_func and base_class. So
> I would pass it my _setProperClass function and Thing. The mapper
> will build against Thing and then run _setProperClass against the
> instance. Yeah, I'm cheating, cause that's kind of basically what I'm
> doing now. I'm just not sure how else to achieve the functionality
> I'm looking for.
ah well making polymorphic_on optionally a callable i can see..i
wonder if someone has suggested that already. thats a good way to
provide this hook for you in just the right place (oh you know what,
i think hibernate allows something like this). not sure what you
mean by "base_class" unless you just mean the existing "inherits"
argument to mapper(). however, didnt you say that your class
attributes come from a different table ? in that case this is still
not going to work...if youre relying upon eager loading of related ,
multiple sets of rows, thats not available until well after the
polymorphic decisions have been made. the most that polymorhpic_func
could get is the first row with the Thing's primary key in it.
As a temporary detour, I was curious if this little hack does it too
- change your metaclass to not generate any new mappers. Just have
one Thing mapper, and instead of the metaclass making a new mapper
when it sees Server (and all the other classes), do this:
from sqlalchemy.orm import mapperlib
thing_mapper = class_mapper(Thing)
mapperlib.mapper_registry[mapperlib.ClassKey(Server, None)] =
thing_mapper
that will just make Server be linked to the same mapper as that of
Thing. combine that with your populate_instance() extension as
usual. just wondering if that works all the way through. this
wouldnt use any SA inheritance/polymorphism or anything like that.
** time passes **
didn't work ? OK, if the polymorphic function were to work, heres
the patch for making polymorphic_on optionally a callable, which gets
sent the query context and row. you return a "discriminiator"
string, which is the string that is matched to a mapper's
"polymorphic_identity" attribute. but here you have to be able to
decide the correct class based on the first result row with that
Thing's primary key, and not a full set of related rows:
Index: lib/sqlalchemy/orm/mapper.py
===================================================================
--- lib/sqlalchemy/orm/mapper.py (revision 3676)
+++ lib/sqlalchemy/orm/mapper.py (working copy)
@@ -324,7 +324,10 @@
self.inherits._add_polymorphic_mapping
(self.polymorphic_identity, self)
if self.polymorphic_on is None:
if self.inherits.polymorphic_on is not None:
- self.polymorphic_on =
self.mapped_table.corresponding_column(self.inherits.polymorphic_on,
keys_ok=True, raiseerr=False)
+ if callable(self.inherits.polymorphic_on):
+ self.polymorphic_on =
self.inherits.polymorphic_on
+ else:
+ self.polymorphic_on =
self.mapped_table.corresponding_column(self.inherits.polymorphic_on,
keys_ok=True, raiseerr=False)
else:
raise exceptions.ArgumentError("Mapper '%s'
specifies a polymorphic_identity of '%s', but no mapper in it's
hierarchy specifies the 'polymorphic_on' column argument" % (str
(self), self.polymorphic_identity))
@@ -686,7 +689,11 @@
props[key] =
self.select_table.corresponding_column(prop)
elif (isinstance(prop, list) and
expression.is_column(prop[0])):
props[key] =
[self.select_table.corresponding_column(c) for c in prop]
- self.__surrogate_mapper = Mapper(self.class_,
self.select_table, non_primary=True, properties=props,
_polymorphic_map=self.polymorphic_map,
polymorphic_on=self.select_table.corresponding_column
(self.polymorphic_on), primary_key=self.primary_key_argument)
+ if not callable(self.polymorphic_on):
+ polymorphic_on =
self.select_table.corresponding_column(self.polymorphic_on)
+ else:
+ polymorphic_on = self.polymorphic_on
+ self.__surrogate_mapper = Mapper(self.class_,
self.select_table, non_primary=True, properties=props,
_polymorphic_map=self.polymorphic_map, polymorphic_on=polymorphic_on,
primary_key=self.primary_key_argument)
def _compile_class(self):
"""If this mapper is to be a primary mapper (i.e. the
@@ -1346,7 +1353,10 @@
row = ret
if not skip_polymorphic and self.polymorphic_on is not None:
- discriminator = row[self.polymorphic_on]
+ if callable(self.polymorphic_on):
+ discriminator = self.polymorphic_on(context, row)
+ else:
+ discriminator = row[self.polymorphic_on]
if discriminator is not None:
mapper = self.polymorphic_map[discriminator]
if mapper is not self:
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/sqlalchemy?hl=en
-~----------~----~----~----~------~----~------~--~---