11# -*- coding: utf-8 -*-
2- import logging
32import json
43from datetime import datetime
54from hashlib import sha256
5+
66from server import db
77
8+
89class IndexMannager :
910 """
1011 Allows efficient pagination for a small specified set of queries.
@@ -25,17 +26,17 @@ class IndexMannager:
2526 def __init__ (self , pageSize = 10 , maxPages = 100 ):
2627 """
2728 :param pageSize: How many items per page
28- :type pageSkel : int
29+ :type pageSize : int
2930 :param maxPages: How many pages are build. Items become unreachable if the amount of items
3031 exceed pageSize*maxPages (ie. if a forum-thread has more than pageSize*maxPages Posts, Posts
3132 after that barrier won't show up).
32- :return:
33+ :type maxPages: int
3334 """
3435 self .pageSize = pageSize
3536 self .maxPages = maxPages
3637 self ._cache = {}
3738
38- def keyFromQuery (self , query ):
39+ def keyFromQuery (self , query ):
3940 """
4041 Derives a unique Database-Key from a given query.
4142 This Key is stable regardless in which order the filter have been applied
@@ -45,61 +46,62 @@ def keyFromQuery(self, query ):
4546 :returns: string
4647 """
4748
48- assert isinstance ( query , db .Query )
49- origFilter = [ (x , y ) for x , y in query .getFilter ().items () ]
49+ assert isinstance (query , db .Query )
50+ origFilter = [(x , y ) for x , y in query .getFilter ().items ()]
5051 for k , v in query .getOrders ():
51- origFilter .append ( ("__%s =" % k , v ) )
52- origFilter .sort ( key = lambda x : x [0 ] )
53- filterKey = "" .join ( ["%s%s" % (x , y ) for x , y in origFilter ] )
54- return ( sha256 ( filterKey ).hexdigest () )
55-
56- def getOrBuildIndex (self , origQuery ):
52+ origFilter .append (("__%s =" % k , v ))
53+ if query .amount :
54+ origFilter .append (("__pagesize =" , self .pageSize ))
55+ origFilter .sort (key = lambda sx : sx [0 ])
56+ filterKey = "" .join (["%s%s" % (x , y ) for x , y in origFilter ])
57+ return sha256 (filterKey ).hexdigest ()
58+
59+ def getOrBuildIndex (self , origQuery ):
5760 """
5861 Builds a specific index based on origQuery AND local variables (self.indexPage and self.indexMaxPage)
5962 Returns a list of starting-cursors for each page.
6063 You probably shouldn't call this directly. Use cursorForQuery.
6164
6265 :param origQuery: Query to build the index for
6366 :type origQuery: db.Query
64- :param key: DB-Key to save the index to
65- :type key: string
6667 :returns: []
6768 """
68- key = self .keyFromQuery ( origQuery )
69- if key in self ._cache : # We have it cached
70- return ( self ._cache [ key ] )
71- #We dont have it cached - try to load it from DB
69+ key = self .keyFromQuery (origQuery )
70+ if key in self ._cache : # We have it cached
71+ return self ._cache [key ]
72+ # We don't have it cached - try to load it from DB
7273 try :
73- index = db .Get ( db .Key .from_path ( self ._dbType , key ) )
74- res = json .loads ( index ["data" ] )
75- self ._cache [ key ] = res
76- return ( res )
77- except db .EntityNotFoundError : # Its not in the datastore, too
74+ index = db .Get (db .Key .from_path (self ._dbType , key ) )
75+ res = json .loads (index ["data" ])
76+ self ._cache [key ] = res
77+ return res
78+ except db .EntityNotFoundError : # Its not in the datastore, too
7879 pass
79- #We dont have this index yet.. Build it
80- #Clone the original Query
81- queryRes = origQuery .clone ( keysOnly = True ).datastoreQuery .Run ( limit = self .maxPages * self .pageSize )
82- #Build-Up the index
83- res = [ ]
84- i = 0
85- previousCursor = None #The first page dosnt have any cursor
86- for discardedKey in queryRes :
87- if i % self .pageSize == 0 :
88- res .append ( previousCursor )
89- if i % self .pageSize == (self .pageSize - 1 ) and i > 1 :
90- previousCursor = str ( queryRes .cursor ().urlsafe () )
91- i += 1
92- if len ( res ) == 0 : # Ensure that the first page exists
93- res .append ( None )
94- entry = db .Entity ( self ._dbType , name = key )
95- entry [ "data" ] = json .dumps ( res )
96- entry [ "creationdate" ] = datetime .now ()
97- db .Put ( entry )
98- return ( res )
99-
100- def cursorForQuery (self , query , page ):
101- """
102- Returns the starting-cursor for the given query and page using an index.
80+ # We don't have this index yet.. Build it
81+ # Clone the original Query
82+ queryRes = origQuery .clone (keysOnly = True ).datastoreQuery .Run (limit = self .maxPages * self .pageSize )
83+ # Build-Up the index
84+ res = list ()
85+ previousCursor = None # The first page dosnt have any cursor
86+
87+ # enumerate is slightly faster than a manual loop counter
88+ for counter , discardedKey in enumerate (queryRes ):
89+ if counter % self .pageSize == 0 :
90+ res .append (previousCursor )
91+ if counter % self .pageSize == (self .pageSize - 1 ):
92+ previousCursor = str (queryRes .cursor ().urlsafe ())
93+
94+ if not len (res ): # Ensure that the first page exists
95+ res .append (None )
96+
97+ entry = db .Entity (self ._dbType , name = key )
98+ entry ["data" ] = json .dumps (res )
99+ entry ["creationdate" ] = datetime .now ()
100+ db .Put (entry )
101+ return res
102+
103+ def cursorForQuery (self , query , page ):
104+ """Returns the starting-cursor for the given query and page using an index.
103105
104106 .. WARNING:
105107
@@ -114,32 +116,36 @@ def cursorForQuery(self, query, page ):
114116 :returns: String-Cursor or None if no cursor is applicable
115117 """
116118 page = int (page )
117- pages = self .getOrBuildIndex ( query )
118- if page > 0 and len ( pages ) > page :
119- return ( db .Cursor ( urlsafe = pages [ page ] ) )
119+ pages = self .getOrBuildIndex (query )
120+ if 0 < page < len (pages ) :
121+ return db .Cursor (urlsafe = pages [page ] )
120122 else :
121- return ( None )
123+ return None
122124
123- def getPages (self , query ):
125+ def getPages (self , query ):
124126 """
125127 Returns a list of all starting-cursors for this query.
126128 The first element is always None as the first page doesn't
127129 have any start-cursor
128130 """
129- return ( self .getOrBuildIndex ( query ) )
131+ return self .getOrBuildIndex (query )
130132
131- def refreshIndex (self , query ):
133+ def refreshIndex (self , query ):
132134 """
133135 Refreshes the Index for the given query
134136 (Actually it removes it from the db so it gets rebuild on next use)
135137
136138 :param query: Query for which the index should be refreshed
137139 :type query: db.Query
138140 """
139- key = self .keyFromQuery ( query )
141+ key = self .keyFromQuery (query )
140142 try :
141- db .Delete ( db .Key .from_path ( self ._dbType , key ) )
143+ db .Delete (db .Key .from_path (self ._dbType , key ) )
142144 except db .EntityNotFoundError :
143145 pass
144- if key in self ._cache :
145- del self ._cache [ key ]
146+
147+ # try/except is faster than if clause
148+ try :
149+ del self ._cache [key ]
150+ except :
151+ pass
0 commit comments