[Sqlalchemy-commits] [1101] sqlalchemy/trunk/examples: added new 'polymorphic' example.
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-03-06 02:27:24
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><style type="text/css"><!-- #msg dl { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; } #msg dt { float: left; width: 6em; font-weight: bold; } #msg dt:after { content:':';} #msg dl, #msg dt, #msg ul, #msg li { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; } #msg dl a { font-weight: bold} #msg dl a:link { color:#fc3; } #msg dl a:active { color:#ff0; } #msg dl a:visited { color:#cc6; } h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; } #msg pre { overflow: auto; background: #ffc; border: 1px #fc0 solid; padding: 6px; } #msg ul, pre { overflow: auto; } #patch { width: 100%; } #patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;} #patch .propset h4, #patch .binary h4 {margin:0;} #patch pre {padding:0;line-height:1.2em;margin:0;} #patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;} #patch .propset .diff, #patch .binary .diff {padding:10px 0;} #patch span {display:block;padding:0 10px;} #patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;} #patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;} #patch .lines, .info {color:#888;background:#fff;} --></style> <title>[1101] sqlalchemy/trunk/examples: added new 'polymorphic' example.</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1101</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-03-05 20:27:13 -0600 (Sun, 05 Mar 2006)</dd> </dl> <h3>Log Message</h3> <pre>added new 'polymorphic' example. still trying to understand it :) . fixes to relation to enable it to locate "direction" more consistently with inheritance relationships more tweaks to parenthesizing subqueries, unions, etc.</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemyansisqlpy">sqlalchemy/trunk/lib/sqlalchemy/ansisql.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemymappingmapperpy">sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemymappingpropertiespy">sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemysqlpy">sqlalchemy/trunk/lib/sqlalchemy/sql.py</a></li> </ul> <h3>Added Paths</h3> <ul> <li>sqlalchemy/trunk/examples/polymorph/</li> <li><a href="#sqlalchemytrunkexamplespolymorphpolymorphpy">sqlalchemy/trunk/examples/polymorph/polymorph.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunkexamplespolymorphpolymorphpy"></a> <div class="addfile"><h4>Added: sqlalchemy/trunk/examples/polymorph/polymorph.py (1100 => 1101)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/examples/polymorph/polymorph.py 2006-03-06 01:17:12 UTC (rev 1100) +++ sqlalchemy/trunk/examples/polymorph/polymorph.py 2006-03-06 02:27:13 UTC (rev 1101) </span><span class="lines">@@ -0,0 +1,131 @@ </span><ins>+from sqlalchemy import * +import sys + +# this example illustrates how to create a relationship to a list of objects, +# where each object in the list has a different type. The typed objects will +# extend from a common base class, although this same approach can be used +# with + +db = create_engine('sqlite://', echo=True, echo_uow=False) + +# a table to store companies +companies = Table('companies', db, + Column('company_id', Integer, primary_key=True), + Column('name', String(50))).create() + +# we will define an inheritance relationship between the table "people" and "engineers", +# and a second inheritance relationship between the table "people" and "managers" +people = Table('people', db, + Column('person_id', Integer, primary_key=True), + Column('company_id', Integer, ForeignKey('companies.company_id')), + Column('name', String(50))).create() + +engineers = Table('engineers', db, + Column('person_id', Integer, ForeignKey('people.person_id'), primary_key=True), + Column('description', String(50))).create() + +managers = Table('managers', db, + Column('person_id', Integer, ForeignKey('people.person_id'), primary_key=True), + Column('description', String(50))).create() + + +# create our classes. The Engineer and Manager classes extend from Person. +class Person(object): + def __repr__(self): + return "Ordinary person %s" % self.name +class Engineer(Person): + def __repr__(self): + return "Engineer %s, description %s" % (self.name, self.description) +class Manager(Person): + def __repr__(self): + return "Manager %s, description %s" % (self.name, self.description) +class Company(object): + def __repr__(self): + return "Company %s" % self.name + +# next we assign Person mappers. Since these are the first mappers we are +# creating for these classes, they automatically become the "primary mappers", which +# define the dependency relationships between the classes, so we do a straight +# inheritance setup, i.e. no modifications to how objects are loaded or anything like that. +assign_mapper(Person, people) +assign_mapper(Engineer, engineers, inherits=Person.mapper) +assign_mapper(Manager, managers, inherits=Person.mapper) + +# next, we define a query that is going to load Managers and Engineers in one shot. +# this query is tricky since the managers and engineers tables contain the same "description" column, +# so we set up a full blown select() statement that uses aliases for the description +# column. The select() statement is also given an alias 'pjoin', since the mapper requires +# that all Selectables have a name. +# +# TECHNIQUE - when you want to load a certain set of objects from a in one query, all the +# columns in the Selectable must have unique names. Dont worry about mappers at this point, +# just worry about making a query where if you were to view the results, you could tell everything +# you need to know from each row how to construct an object instance from it. this is the +# essence of "resultset-based-mapping", which is the core ideology of SQLAlchemy. +# +person_join = select( + [people, managers.c.description,column("'manager'").label('type')], + people.c.person_id==managers.c.person_id).union( + select( + [people, engineers.c.description, column("'engineer'").label('type')], + people.c.person_id==engineers.c.person_id)).alias('pjoin') + + +# lets print out what this Selectable looks like. The mapper is going to take the selectable and +# Select off of it, with the flag "use_labels" which indicates to prefix column names with the table +# name. So here is what our mapper will see: +print "Person selectable:", str(person_join.select(use_labels=True)), "\n" + + +# MapperExtension object. +class PersonLoader(MapperExtension): + def create_instance(self, mapper, row, imap, class_): + if row['pjoin_type'] =='engineer': + return Engineer() + elif row['pjoin_type'] =='manager': + return Manager() + else: + return Person() +ext = PersonLoader() + +# set up the polymorphic mapper, which maps the person_join we set up to +# the Person class, using an instance of PersonLoader. Note that even though +# this mapper is against Person, its not going to screw up the normal operation +# of the Person object since its not the "primary" mapper. In reality, we could even +# make this mapper against some other class we dont care about since the creation of +# objects is hardcoded. +people_mapper = mapper(Person, person_join, extension=ext) + +assign_mapper(Company, companies, properties={ + 'employees': relation(people_mapper), + 'engineers': relation(Engineer, private=True), + 'managers':relation(Manager, private=True) +}) + + +c = Company(name='company1') +c.employees.append(Manager(name='pointy haired boss', description='manager1')) +c.employees.append(Engineer(name='dilbert', description='engineer1')) +c.employees.append(Engineer(name='wally', description='engineer2')) +c.employees.append(Manager(name='jsmith', description='manager2')) +objectstore.commit() + +objectstore.clear() + +c = Company.get(1) +for e in c.employees: + print e, e._instance_key + +print "\n" + +dilbert = c.employees[1] +dilbert.description = 'hes dibert!' +objectstore.commit() + +objectstore.clear() +c = Company.get(1) +for e in c.employees: + print e, e._instance_key + +objectstore.delete(c) +objectstore.commit() </ins><span class="cx">\ No newline at end of file </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyansisqlpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/ansisql.py (1100 => 1101)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/ansisql.py 2006-03-06 01:17:12 UTC (rev 1100) +++ sqlalchemy/trunk/lib/sqlalchemy/ansisql.py 2006-03-06 02:27:13 UTC (rev 1101) </span><span class="lines">@@ -240,7 +240,10 @@ </span><span class="cx"> text = string.join([self.get_str(c) for c in cs.selects], " " + cs.keyword + " ") </span><span class="cx"> for tup in cs.clauses: </span><span class="cx"> text += " " + tup[0] + " " + self.get_str(tup[1]) </span><del>- self.strings[cs] = text </del><ins>+ if cs.parens: + self.strings[cs] = "(" + text + ")" + else: + self.strings[cs] = text </ins><span class="cx"> self.froms[cs] = "(" + text + ")" </span><span class="cx"> </span><span class="cx"> def visit_binary(self, binary): </span><span class="lines">@@ -368,7 +371,7 @@ </span><span class="cx"> </span><span class="cx"> text += self.visit_select_postclauses(select) </span><span class="cx"> </span><del>- if getattr(select, 'useparens', False): </del><ins>+ if getattr(select, 'parens', False): </ins><span class="cx"> self.strings[select] = "(" + text + ")" </span><span class="cx"> else: </span><span class="cx"> self.strings[select] = text </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemymappingmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py (1100 => 1101)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py 2006-03-06 01:17:12 UTC (rev 1100) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py 2006-03-06 02:27:13 UTC (rev 1101) </span><span class="lines">@@ -952,7 +952,6 @@ </span><span class="cx"> </span><span class="cx"> def class_mapper(class_): </span><span class="cx"> """given a class, returns the primary Mapper associated with the class.""" </span><del>- return mapper_registry[class_] </del><span class="cx"> try: </span><span class="cx"> return mapper_registry[class_] </span><span class="cx"> except KeyError: </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemymappingpropertiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py (1100 => 1101)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py 2006-03-06 01:17:12 UTC (rev 1100) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py 2006-03-06 02:27:13 UTC (rev 1101) </span><span class="lines">@@ -229,7 +229,8 @@ </span><span class="cx"> </span><span class="cx"> def _get_direction(self): </span><span class="cx"> """determines our 'direction', i.e. do we represent one to many, many to many, etc.""" </span><del>-# print self.key, repr(self.parent.table.name), repr(self.parent.primarytable.name), repr(self.foreignkey.table.name) </del><ins>+ #print self.key, repr(self.parent.table.name), repr(self.parent.primarytable.name), repr(self.foreignkey.table.name), repr(self.target), repr(self.foreigntable.name) + </ins><span class="cx"> if self.parent.table is self.target: </span><span class="cx"> if self.foreignkey.primary_key: </span><span class="cx"> return PropertyLoader.MANYTOONE </span><span class="lines">@@ -237,9 +238,9 @@ </span><span class="cx"> return PropertyLoader.ONETOMANY </span><span class="cx"> elif self.secondaryjoin is not None: </span><span class="cx"> return PropertyLoader.MANYTOMANY </span><del>- elif self.foreigntable == self.target: </del><ins>+ elif self.foreigntable is self.target or self.foreigntable in self.mapper.tables: </ins><span class="cx"> return PropertyLoader.ONETOMANY </span><del>- elif self.foreigntable == self.parent.table: </del><ins>+ elif self.foreigntable is self.parent.table or self.foreigntable in self.parent.tables: </ins><span class="cx"> return PropertyLoader.MANYTOONE </span><span class="cx"> else: </span><span class="cx"> raise ArgumentError("Cant determine relation direction") </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemysqlpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/sql.py (1100 => 1101)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/sql.py 2006-03-06 01:17:12 UTC (rev 1100) +++ sqlalchemy/trunk/lib/sqlalchemy/sql.py 2006-03-06 02:27:13 UTC (rev 1101) </span><span class="lines">@@ -454,7 +454,7 @@ </span><span class="cx"> # assume *other is a list of selects. </span><span class="cx"> # so put them in a UNION. if theres only one, you just get one SELECT </span><span class="cx"> # statement out of it. </span><del>- return self._compare('IN', union(*other)) </del><ins>+ return self._compare('IN', union(parens=True, *other)) </ins><span class="cx"> def startswith(self, other): </span><span class="cx"> return self._compare('LIKE', str(other) + "%") </span><span class="cx"> def endswith(self, other): </span><span class="lines">@@ -1123,6 +1123,7 @@ </span><span class="cx"> self.keyword = keyword </span><span class="cx"> self.selects = selects </span><span class="cx"> self.use_labels = kwargs.pop('use_labels', False) </span><ins>+ self.parens = kwargs.pop('parens', False) </ins><span class="cx"> self.oid_column = selects[0].oid_column </span><span class="cx"> for s in self.selects: </span><span class="cx"> s.group_by(None) </span><span class="lines">@@ -1209,7 +1210,8 @@ </span><span class="cx"> def visit_compound_select(self, cs): </span><span class="cx"> self.visit_select(cs) </span><span class="cx"> for s in cs.selects: </span><del>- s.useparens = False </del><ins>+ s.parens = False + print "BUT", id(cs), cs.parens </ins><span class="cx"> def visit_column(self, c):pass </span><span class="cx"> def visit_table(self, c):pass </span><span class="cx"> def visit_select(self, select): </span><span class="lines">@@ -1217,7 +1219,7 @@ </span><span class="cx"> return </span><span class="cx"> select.is_where = self.is_where </span><span class="cx"> select.issubquery = True </span><del>- select.useparens = True </del><ins>+ select.parens = True </ins><span class="cx"> if getattr(select, '_correlated', None) is None: </span><span class="cx"> select._correlated = self.select._froms </span><span class="cx"> </span></span></pre> </div> </div> </body> </html> |