Skip to content
This repository was archived by the owner on Sep 29, 2023. It is now read-only.

Commit 10fe12e

Browse files
Merge pull request #76 from viur-framework/feature/Indexmanger_rework
Polishing IndexManager
2 parents 6b738d3 + b534cbf commit 10fe12e

1 file changed

Lines changed: 64 additions & 58 deletions

File tree

indexes.py

Lines changed: 64 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# -*- coding: utf-8 -*-
2-
import logging
32
import json
43
from datetime import datetime
54
from hashlib import sha256
5+
66
from server import db
77

8+
89
class 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

Comments
 (0)