使用装饰器管理缓存

这个来自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

 

你可能感兴趣的:(装饰器)