使用装饰器管理缓存

这个来自python高手飞龙非龙的代码

# -*- coding: utf-8 -*-
#
# Copyright(c) 2010 poweredsites.org
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import hashlib
from datetime import datetime, timedelta
from decorator import decorator
from tornado.escape import utf8
from tornado.options import options

from poweredsites.libs.handler import BaseHandler
from poweredsites.db.caches import Page, Cache
from poweredsites.db import conn

__all__ = ("cache", "page", "mem", "key_gen", "remove")

_mem_caches = {}

def cache(expire=7200, condition="", key="", anonymous=False):
    """Decorator which caches the value of a method in a handler or a module.    
    
    expire: Cache will be expired time from now in seconds.
    condition: If the result of sql condition has changed, then cache expired. 
    key: The unique key of the cache identify in the DB 
    
    cache_pre: A method which is defined in self(handler or module), 
                it always be executed before cache or get from cache.
                
    cache_condition: A property which is defined in self(handler or module), it
                is used to construct a complex condition.
    """
    def wrapper(func, self, *args, **kwargs):
        now = datetime.now()
        if key:
            c = key
        else:
            c = self.__class__.__name__ + func.__name__
        k, handler, cond = key_gen(self, condition, anonymous, c, *args, **kwargs)

        value = Cache().findby_key(k)
        new_cond = {}

        if _valid_cache(value, handler, cond, new_cond, anonymous, now):
            if new_cond:
                c = Cache()
                c.key = k
                c.condition = new_cond["condition"]
                c.save(value["_id"])
            return value["value"]
        else:
            val = func(self, *args, **kwargs)

            c = Cache()
            # need key, or save will not work
            c.key = k
            c.value = val
            c.expire = now + timedelta(seconds=expire)
            c.condition = new_cond.get("condition", "")

            if value:
                c.save(value["_id"])
            else:
                c.insert()

            return val

    return decorator(wrapper)

def page(expire=7200, condition="", key="", anonymous=False):
    """Decorator which caches a whole page(headers + html) in a handler
    
    expire: Cache will be expired time from now in seconds.   
    condition: If the result of sql condition has changed, then cache expired.
    key: The unique key of the cache identify in the DB 
    
    """
    def wrapper(func, self, *args, **kwargs):
        now = datetime.now()
        if key:
            c = key
        else:
            c = self.__class__.__name__ + func.__name__
        k, handler, cond = key_gen(self, condition, anonymous, c, *args, **kwargs)

        value = Cache().findby_key(k)
        new_cond = {}

        is_valid = _valid_cache(value, handler, cond, new_cond, anonymous, now)
        if is_valid and value["status"] in (200, 304) and value["chunk"]:
            if new_cond:
                c = Page()
                c.key = k
                c.condition = new_cond["condition"]
                c.save(value["_id"])

            # finish request with cache chunk and headers
            self.set_status(value["status"])
            self.set_header("Content-Type", utf8(value["headers"]["Content-Type"]))
            self.write(utf8("".join(value["chunk"])))
        else:
            func(self, *args, **kwargs)

            c = Page()
            c.key = k
            c.status = self._status_code
            c.headers = self._headers
            c.chunk = self._write_buffer
            c.expire = now + timedelta(seconds=expire)
            c.condition = new_cond.get("condition", "")

            if value:
                c.save(value["_id"])
            else:
                c.insert()

    return decorator(wrapper)

def mem(expire=7200, key=""):
    """Mem cache to python dict by key"""
    def wrapper(func, self, *args, **kwargs):
        now = datetime.now()
        if key:
            c = key
        else:
            c = self.__class__.__name__ + func.__name__
        k, handler, cond = key_gen(self, "", False, c, *args, **kwargs)

        value = _mem_caches.get(k, None)
        if _valid_cache(value, handler, cond, [], False, now):
            return value["value"]
        else:
            val = func(self, *args, **kwargs)
            _mem_caches[k] = {"value":val, "expire":now}

            return val

    return decorator(wrapper)

def key_gen(self, condition, anonymous, key, *args, **kwargs):
    code = hashlib.md5()

    code.update(str(key))

    # copy args to avoid sort original args
    c = list(args[:])
    # sort c to avoid generate different key when args is the same
    # but sequence is different
    c.sort()
    c = [str(v) for v in c]
    code.update("".join(c))

    c = ["%s=%s" % (k, v) for k, v in kwargs]
    c.sort()
    code.update("".join(c))

    if isinstance(self, BaseHandler):
        handler = self
    else:
        handler = getattr(self, "handler")

    # execute cache_pre before get cache_condition
    # so we can construct a complex condition.
    cache_pre = getattr(self, "cache_pre", None)
    if cache_pre:
        cache_pre(*args, **kwargs)

    if not condition:
        condition = getattr(self, "cache_condition", "")

    # also update condition to key, so the same func 
    # has diff caches if there condition is diff(cache_condtion)
    code.update(str(condition))

    # cache for every users if anonymous is False
    if not anonymous and handler.current_user:
        # add userid= prefix to avoid key conflict
        # (eg. siteid + userid maybe equal another siteid)
        code.update(str("userid=%s" % handler.current_user.id))

    # page argument as key by default
    # Todo: add a argument option for key gen like condition
    page = handler.get_argument("page", "")
    if page:
        code.update(str("page=%s" % page))

    # cache for different host(the same uri may have different subdomain)
    code.update(handler.request.host)

    return code.hexdigest(), handler, condition

def remove(key):
    """Remove a cache's value."""
    c = Cache()
    v = c.findby_key(key)
    if v:
        c.remove(v["_id"])

def _valid_cache(value, handler, condition, new_condtion, anonymous, now):
    if not options.cache_enabled:
        return False

    if anonymous and handler.current_user:
        return False

    if condition:
        old_cond = value.get("condition", "") if value else ""
        rows = conn.mysql.query(condition)

        new_cond = ""
        for r in rows:
            new_cond += str(r)

        # unify to utf8, the string result return by pymongo is unicode
        new_cond = utf8(new_cond)
        old_cond = utf8(old_cond if old_cond else "")

        if old_cond != new_cond:
            new_condtion["condition"] = new_cond
            return False

    if value:
        if value["expire"] > now:
            return True
        else:
            return False
    else:
        return False

 

posted @ 2013-02-23 14:49  网恋被骗5元  阅读(294)  评论(0编辑  收藏  举报