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 sqlalchemy@googlegroups.com
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
-~----------~----~----~----~------~----~------~--~---

Reply via email to