Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

I think I've seen this ORM somewhere before...

Changeset 405

Show
Ignore:
Timestamp:
02/01/07 00:03:53
Author:
fumanchu
Message:

New arena.view, sum, distinct, count, range methods (just like the sandbox methods, but without using a cache).

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/arenas.py

    r404 r405  
    258258                if not copy_only: 
    259259                    self._registered_classes[cls] = new_store 
     260     
     261    def view(self, cls, attrs, expr=None, **kwargs): 
     262        """Yield tuples of attrs for the given cls which match the expr. 
     263         
     264        cls: The Unit subclass for which to yield property tuples. 
     265        attrs: a sequence of strings; each should be the name of 
     266            a UnitProperty on the given cls. 
     267        expr: a lambda or logic.Expression. If provided, data will only 
     268            be yielded for units of the given cls which match the expr. 
     269        **kwargs: additional expr filters in name=value format. 
     270         
     271        Each yielded value will be a list of values, in the same order as 
     272        the attrs arg. This facilitates unpacking in iterative consumer 
     273        code like: 
     274         
     275        for id, name in sandbox.view(Invoice, ['ID', 'Name'], f): 
     276            print id, ": ", name 
     277         
     278        This is generally much faster than recall, and should be preferred 
     279        for performance-sensitive code. 
     280        """ 
     281        if expr and not isinstance(expr, logic.Expression): 
     282            expr = logic.Expression(expr) 
     283        if kwargs: 
     284            f = logic.filter(**kwargs) 
     285            if expr: 
     286                expr += f 
     287            else: 
     288                expr = f 
     289         
     290        if self.logflags & logflags.VIEW: 
     291            self.log("VIEW %s [%s]: %s" % (cls.__name__, attrs, expr)) 
     292         
     293        for row in self.storage(cls).view(cls, attrs, expr): 
     294            yield row 
     295     
     296    def sum(self, cls, attr, expr=None, **kwargs): 
     297        """Sum of all non-None values for the given cls.attr.""" 
     298        expr = logic.Expression(lambda x: getattr(x, attr) != None) + expr 
     299        return sum([row[0] for row in self.view(cls, (attr,), expr, **kwargs)]) 
     300     
     301    def distinct(self, cls, attrs, expr=None, **kwargs): 
     302        """List of distinct UnitProperty tuples for the given cls. 
     303         
     304        If only one attribute is specified, a list of values will be returned. 
     305        If more than one attribute is specified, a zipped list will be returned. 
     306         
     307        Notice that you can also use this function as a count() function 
     308        (in fact it's the only way to do it) by using attrs = ['ID']. 
     309        """ 
     310        if expr and not isinstance(expr, logic.Expression): 
     311            expr = logic.Expression(expr) 
     312        if kwargs: 
     313            f = logic.filter(**kwargs) 
     314            if expr: 
     315                expr += f 
     316            else: 
     317                expr = f 
     318         
     319        if self.logflags & logflags.VIEW: 
     320            self.log("DISTINCT %s [%s]: %s" % (cls.__name__, attrs, expr)) 
     321         
     322        seen = {} 
     323        for row in self.storage(cls).distinct(cls, attrs, expr): 
     324            if row not in seen: 
     325                seen[row] = None 
     326         
     327        seen = seen.keys() 
     328        seen.sort() 
     329        if len(attrs) == 1: 
     330            seen = [x[0] for x in seen] 
     331        return seen 
     332     
     333    def count(self, cls, expr): 
     334        """Number of Units of the given cls which match the given expr.""" 
     335        return len(self.distinct(cls, cls.identifiers, expr)) 
     336     
     337    def range(self, cls, attr, expr=None, **kwargs): 
     338        """Distinct, non-None attr values (ordered and continuous, if possible). 
     339         
     340        If the given attribute is a known discrete, ordered type 
     341        (like int, long, datetime.date), this returns the closed interval: 
     342             
     343            [min(attr), ..., max(attr)] 
     344         
     345        That is, all possible values will be output between min and max, 
     346        even if they do not appear in the dataset. 
     347         
     348        If the given attribute is not reasonably discrete (e.g., str, 
     349        unicode, or float) then all distinct, non-None values are returned 
     350        (sorted, if possible). 
     351        """ 
     352        existing = [x for x in self.distinct(cls, [attr], expr, **kwargs) 
     353                    if x is not None] 
     354        if not existing: 
     355            return [] 
     356         
     357        attr_type = getattr(cls, attr).type 
     358        if issubclass(attr_type, (int, long)): 
     359            return range(min(existing), max(existing) + 1) 
     360        else: 
     361            try: 
     362                import datetime 
     363            except ImportError: 
     364                pass 
     365            else: 
     366                if issubclass(attr_type, datetime.date): 
     367                    def date_gen(): 
     368                        start, end = min(existing), max(existing) 
     369                        for d in range((end + 1) - start): 
     370                            yield start + datetime.timedelta(d) 
     371                    return date_gen() 
     372         
     373        try: 
     374            existing.sort() 
     375        except TypeError: 
     376            pass 
     377         
     378        return existing 
    260379 
    261380 
  • trunk/test/zoo_fixture.py

    r404 r405  
    719719        finally: 
    720720            box.flush_all() 
    721              
     721     
    722722    def test_8_CustomAssociations(self): 
    723723        box = arena.new_sandbox() 
     
    744744        finally: 
    745745            box.flush_all() 
     746     
     747    def test_9_arena_views(self): 
     748        # views 
     749        legs = [x[0] for x in arena.view(Animal, ['Legs'])] 
     750        legs.sort() 
     751        self.assertEqual(legs, [1, 2, 2, 2, 2, 2, 4, 4, 4, 4, 100, 1000000]) 
     752         
     753        expected = {'Leopard': 73.5, 
     754                    'Slug': .75, 
     755                    'Tiger': None, 
     756                    'Lion': None, 
     757                    'Bear': None, 
     758                    'Ostrich': 103.2, 
     759                    'Centipede': None, 
     760                    'Emperor Penguin': None, 
     761                    'Adelie Penguin': None, 
     762                    'Millipede': None, 
     763                    'Ape': None, 
     764                    } 
     765        for species, lifespan in arena.view(Animal, ['Species', 'Lifespan']): 
     766            if expected[species] is None: 
     767                self.assertEqual(lifespan, None) 
     768            else: 
     769                self.assertAlmostEqual(expected[species], lifespan, places=5) 
     770         
     771        expected = [u'Montr\xe9al Biod\xf4me', 'Wild Animal Park'] 
     772        e = (lambda x: x.Founded != None 
     773             and x.Founded <= dejavu.today() 
     774             and x.Founded >= datetime.date(1990, 1, 1)) 
     775        values =  [val[0] for val in arena.view(Zoo, ['Name'], e)] 
     776        for name in expected: 
     777            self.assert_(name in values) 
     778         
     779        # distinct 
     780        legs = arena.distinct(Animal, ['Legs']) 
     781        legs.sort() 
     782        self.assertEqual(legs, [1, 2, 4, 100, 1000000]) 
     783         
     784        # This may raise a warning on some DB's. 
     785        f = (lambda x: x.Species == 'Lion') 
     786        escapees = arena.distinct(Animal, ['Legs'], f) 
     787        self.assertEqual(escapees, [4]) 
     788         
     789        # range should return a sorted list 
     790        legs = arena.range(Animal, 'Legs', lambda x: x.Legs <= 100) 
     791        self.assertEqual(legs, range(1, 101)) 
     792        topics = arena.range(Exhibit, 'Name') 
     793        self.assertEqual(topics, ['The Penguin Encounter', 'Tiger River']) 
     794        vets = arena.range(Vet, 'Name') 
     795        self.assertEqual(vets, ['Charles Schroeder', 'Jim McBain']) 
    746796     
    747797    def test_Iteration(self):