Package pyperry :: Package processors :: Module preload_associations
[frames] | no frames]

Source Code for Module pyperry.processors.preload_associations

 1  from pyperry.errors import AssociationNotFound 
 2   
3 -class PreloadAssociations(object):
4 """ 5 The PreloadAssociations processor looks for any association ids given in 6 a query's 'includes' value and eager loads those associations. The purpose 7 of eager loading (or preloading) is to reduce the number of adapter calls 8 needed to retrieve records in an association, which can yield a significant 9 performance boost if you have a query where you are using many associated 10 models. 11 12 For example, let's assume you are writing a news website and you have 13 a Reporter model and a Story model where each Reporter has many Stories. 14 Now suppose you want to display a list all reporters and their stories on 15 the screen grouped by the reporter's name, you could do this like:: 16 17 reporters = Reporter.order('name').all() 18 for reporter in reporters: 19 print reporter.name 20 for story in reporter.stories(): 21 print "\t%s" % story.title 22 23 If your news website has 20 reporters, you have just executed 1 adapter 24 call to retrieve the list of reporters and then 20 more adapter calls, one 25 for each reporter's stories, for a total of 21 adapter calls. The problem 26 with having too many adapter calls is that typically adapters must read 27 data from a server on the network, which causes your program to wait while 28 the server generates a response and sends it over the network. 29 30 Instead, you can achieve the same results in 2 queries by using the 31 includes method in your query to trigger association preloading. In this 32 modified example, we simple add C{.includes('stories')} to our query, and 33 all of the stories for every report will be retrieved with only one 34 additional adapter call:: 35 36 reporters = Reporter.order('name').includes('stories').all() 37 for reporter in reporters: 38 print reporter.name 39 for story in reporter.stories(): 40 print " - %s" % story.title 41 42 """
43 - def __init__(self, next, options={}):
44 self.next = next 45 self.options = options
46
47 - def __call__(self, **kwargs):
48 results = self.next(**kwargs) 49 if kwargs['mode'] == 'read' and len(results) > 0: 50 self.do_preload(results, **kwargs) 51 return results
52
53 - def do_preload(self, results, **kwargs):
54 """ 55 Eager loads the records for each association in the query's includes. 56 57 """ 58 rel = kwargs['relation'] 59 includes = rel.query().get('includes') or {} 60 61 for association_id in includes.keys(): 62 association = rel.klass.defined_associations.get(association_id) 63 if association is None: raise AssociationNotFound( 64 "unkown association: %s" % association_id) 65 scope = association.scope(results) 66 scope = scope.includes(includes[association_id]) 67 eager_records = scope.all({'modifiers': rel.modifiers_value()}) 68 69 for result in results: 70 self.add_records_to_scope(association, eager_records, result)
71
72 - def add_records_to_scope(self, association, records, result):
73 """ 74 Caches the eager loaded records on the matching target model and 75 association along with the corresponding scope. 76 77 """ 78 scope = association.scope(result) 79 pk = association.primary_key() 80 fk = association.foreign_key 81 82 if association.type() is 'belongs_to': 83 scope._records = [record for record in records if 84 getattr(record, pk) == getattr(result, fk)] 85 86 else: # has_one, has_many 87 scope._records = [record for record in records if 88 getattr(record, fk) == getattr(result, pk)] 89 90 scope_value = scope 91 if not association.collection(): 92 scope_value = scope._records[0] if len(scope._records) > 0 else None 93 94 setattr(result, association.id, scope_value)
95