A simple form of joint controller+template caching can be achieved by using both cached
decorator
and tg_cache
parameter as described in Caching.
While it is more common having to perform some kind of minimal computation in controller to decide which cache key to use and rely on the template caching only, the caching system can be leveraged to turn the whole request in a direct cache hit based on the request parameters.
This can be achieved by relying on the cached
decorator and using render_template()
to
actually render the template during controller execution and caching it together with the controller itself:
from tg import cached, render_template
@cached()
@expose(content_type='text/html')
def cached_func(self, what='about'):
return render_template(dict(page=what, time=time.time()),
'kajiki', 'myproj.templates.cached_func')
Note
While @cached
caches the controller itself, any hook or validation associated to the
controller will still be executed. This might be what you want (for example when tracking
page views through an hook) or it might not, depending on your needs you might want to move
hooks and validation inside the controller itself to ensure they are cached.
Authentication in TurboGears is provided by repoze.who
through the ApplicationAuthMetadata
class
as described in Identification & Authentication Layer.
ApplicationAuthMetadata
provides two major steps in authentication:
- One is authenticating the user itself for the first time (done by
ApplicationAuthMetadata.authenticate
) which is in charge of returning theuser_name
that uniquely identifies the user (or any other unique identifier) that is then received by the other methods to lookup the actual user data.- The second step, which is the metadata provisioning, is performed by
IdentityApplicationWrapper
and receives the previously identified user as returned by the authentication step. This is performed byget_user
,get_groups
andget_permissions
which are in charge of returning the three aforementioned information regarding the user.
It’s easy to see that this second step is usually the one that has most weight over the request throughput as it involves three different queries to the database.
We can easily change the ApplicationAuthMetadata
in our code to rely on the cache to fetch user data instead
of loading it back from database:
from tg import cache
class ApplicationAuthMetadata(TGAuthMetadata):
def __init__(self, sa_auth):
self.sa_auth = sa_auth
def authenticate(self, environ, identity):
# This should be your current authenticate implementation
...
def get_user(self, identity, userid):
identity.update(self._get_cached_auth_metadata(userid))
return identity['user']
def get_groups(self, identity, userid):
return identity['groups'] or []
def get_permissions(self, identity, userid):
return identity['permissions'] or []
def _get_cached_auth_metadata(self, userid):
"""Retrieves the user details from the cache when available"""
auth_cache = cache.get_cache('auth')
auth_metadata = auth_cache.get_value(key=userid,
createfunc=lambda: self._retrieve_auth_metadata(userid),
expiretime=3600)
auth_metadata['user'] = self.sa_auth.dbsession.merge(auth_metadata['user'], load=False)
return auth_metadata
def _retrieve_auth_metadata(self, userid):
"""Retrieves user details from the database"""
user = self.sa_auth.dbsession.query(self.sa_auth.user_class).filter_by(user_name=userid).first()
return {
'user': user,
'groups': user and [g.group_name for g in user.groups],
'permissions': user and [p.permission_name for p in user.permissions]
}
This is usually enough to cache authentication requests in an environment where user data, permissions and groups change rarely. A better cache management, invalidating the user cache whenever the user itself or its permission change, is required for more volatile scenarios.