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