
#
# spyne - Copyright (C) Spyne contributors.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
#

"""The module for memoization stuff.

When you have memory leaks in your daemon, the reason could very well be
reckless usage of the tools here.

These are NOT thread-safe. If you are relying on exactly-one-execution-per-key
behavior in a multithreaded environment, roll your own stuff.
"""


import logging
logger = logging.getLogger(__name__)


MEMOIZATION_STATS_LOG_INTERVAL = 60.0


def _do_log():
    logger.info("%d memoizers", len(memoize.registry))
    for memo in memoize.registry:
        logger.info("%r: %d entries.", memo.func, len(memo.memo))


def start_memoization_stats_logger():
    import threading

    logger.info("Enabling @memoize statistics every %d second(s).",
                                                 MEMOIZATION_STATS_LOG_INTERVAL)

    _do_log()

    t = threading.Timer(MEMOIZATION_STATS_LOG_INTERVAL,
                                                 start_memoization_stats_logger)

    t.daemon = True
    t.start()


class memoize(object):
    """A memoization decorator that keeps caching until reset."""

    registry = []

    def __init__(self, func):
        self.func = func
        self.memo = {}
        memoize.registry.append(self)

    def __call__(self, *args, **kwargs):
        key = self.get_key(args, kwargs)
        if not key in self.memo:
            value = self.func(*args, **kwargs)
            self.memo[key] = value
            return value
        return self.memo.get(key)

    def get_key(self, args, kwargs):
        return tuple(args), tuple(kwargs.items())

    def reset(self):
        self.memo = {}


def memoize_ignore(values):
    """A memoization decorator that ignores values in the 'values' iterable. eg
    let `values = (1, 2)` and `add = lambda x, y: x + y`, the result of
    `add(1, 1)` is not memoized but the result of `add(5, 5)` is."""

    class _memoize_ignored(memoize):
        def __call__(self, *args, **kwargs):
            key = self.get_key(args, kwargs)
            if not key in self.memo:
                value = self.func(*args, **kwargs)
                if not value in values:
                    self.memo[key] = value

                return value
            return self.memo.get(key)

    return _memoize_ignored


class memoize_ignore_none(memoize):
    """A memoization decorator that ignores `None` values. ie when the decorated
    function returns `None`, the value is returned but not memoized.
    """

    def __call__(self, *args, **kwargs):
        key = self.get_key(args, kwargs)
        if not key in self.memo:
            value = self.func(*args, **kwargs)
            if not (value is None):
                self.memo[key] = value

            return value
        return self.memo.get(key)


class memoize_id(memoize):
    """A memoization decorator that keeps caching until reset for unhashable
    types. It works on id()'s of objects instead."""

    def get_key(self, args, kwargs):
        return tuple([id(a) for a in args]), \
                                  tuple([(k, id(v)) for k, v in kwargs.items()])

@@ -1160,22 +1160,23 @@
 %0A%0A%0Adef _
-do_
 log
+_all
 ():%0A    
@@ -1328,16 +1328,366 @@
 emo))%0A%0A%0A
+def _log_func(func):%0A    for memo in memoize.registry:%0A        if memo.func is func.func.im_self.func:%0A            break%0A    else:%0A        logger.error(%22%25r not found in memoization regisry%22, func)%0A        return%0A%0A    logger.info(%22%25r: %25d entries.%22, memo.func, len(memo.memo))%0A    for k, v in memo.memo.items():%0A        logger.info(%22%5Ct%25r: %25r%22, k, v)%0A%0A%0A
 def star
@@ -1713,16 +1713,25 @@
 _logger(
+func=None
 ):%0A    i
@@ -1905,16 +1905,76 @@
     
-_do_log(
+if func is None:%0A        _log_all()%0A    else:%0A        _log_func(func
 )%0A%0A 
@@ -2068,25 +2068,16 @@
         
-         
 start_me
@@ -2098,16 +2098,25 @@
 s_logger
+, (func,)
 )%0A%0A    t

add option to dump contents of a specific memo with start_memoization_stats_logger