# Path: monty/design_patterns.py
# def singleton(cls):
#     """
#     This decorator can be used to create a singleton out of a class.
# 
#     Usage::
# 
#         @singleton
#         class MySingleton():
# 
#             def __init__():
#                 pass
#     """
# 
#     instances = {}
# 
#     def getinstance():
#         if cls not in instances:
#             instances[cls] = cls()
#         return instances[cls]
# 
#     return getinstance
# 
# def cached_class(klass):
#     """
#     Decorator to cache class instances by constructor arguments.
#     This results in a class that behaves like a singleton for each
#     set of constructor arguments, ensuring efficiency.
# 
#     Note that this should be used for *immutable classes only*.  Having
#     a cached mutable class makes very little sense.  For efficiency,
#     avoid using this decorator for situations where there are many
#     constructor arguments permutations.
# 
#     The keywords argument dictionary is converted to a tuple because
#     dicts are mutable; keywords themselves are strings and
#     so are always hashable, but if any arguments (keyword
#     or positional) are non-hashable, that set of arguments
#     is not cached.
#     """
#     cache = {}
# 
#     @wraps(klass, assigned=("__name__", "__module__"), updated=())
#     class _decorated(klass):
#         # The wraps decorator can't do this because __doc__
#         # isn't writable once the class is created
#         __doc__ = klass.__doc__
# 
#         def __new__(cls, *args, **kwargs):
#             """
#             Pass through...
#             :param args:
#             :param kwargs:
#             :return:
#             """
#             key = (cls,) + args + tuple(kwargs.items())
#             try:
#                 inst = cache.get(key, None)
#             except TypeError:
#                 # Can't cache this set of arguments
#                 inst = key = None
#             if inst is None:
#                 # Technically this is cheating, but it works,
#                 # and takes care of initializing the instance
#                 # (so we can override __init__ below safely);
#                 # calling up to klass.__new__ would be the
#                 # "official" way to create the instance, but
#                 # that raises DeprecationWarning if there are
#                 # args or kwargs and klass does not override
#                 # __new__ (which most classes don't), because
#                 # object.__new__ takes no parameters (and in
#                 # Python 3 the warning will become an error)
#                 inst = klass(*args, **kwargs)
#                 # This makes isinstance and issubclass work
#                 # properly
#                 inst.__class__ = cls
#                 if key is not None:
#                     cache[key] = inst
#             return inst
# 
#         def __init__(self, *args, **kwargs):
#             # This will be called every time __new__ is
#             # called, so we skip initializing here and do
#             # it only when the instance is created above
#             pass
# 
#     return _decorated


import unittest
import pickle
from monty.design_patterns import singleton, cached_class
@cached_class
class A:
    def __init__(self, val):
        self.val = val

    def __eq__(self, other):
        return self.val == other.val

    def __getinitargs__(self):
        return (self.val,)

    def __getnewargs__(self):
        return (self.val,)


class CachedClassTest(unittest.TestCase):
    def test_cached_class(self):
        a1a = A(1)
        a1b = A(1)
        a2 = A(2)

        self.assertEqual(id(a1a), id(a1b))
        self.assertNotEqual(id(a1a), id(a2))

    # def test_pickle(self):
    #     a = A(2)
    #     o = pickle.dumps(a)
    #     self.assertEqual(a, pickle.loads(o))



# Path: monty/design_patterns.py
# def singleton(cls):
#     """
#     This decorator can be used to create a singleton out of a class.
# 
#     Usage::
# 
#         @singleton
#         class MySingleton():
# 
#             def __init__():
#                 pass
#     """
# 
#     instances = {}
# 
#     def getinstance():
#         if cls not in instances:
#             instances[cls] = cls()
#         return instances[cls]
# 
#     return getinstance
# 
# def cached_class(klass):
#     """
#     Decorator to cache class instances by constructor arguments.
#     This results in a class that behaves like a singleton for each
#     set of constructor arguments, ensuring efficiency.
# 
#     Note that this should be used for *immutable classes only*.  Having
#     a cached mutable class makes very little sense.  For efficiency,
#     avoid using this decorator for situations where there are many
#     constructor arguments permutations.
# 
#     The keywords argument dictionary is converted to a tuple because
#     dicts are mutable; keywords themselves are strings and
#     so are always hashable, but if any arguments (keyword
#     or positional) are non-hashable, that set of arguments
#     is not cached.
#     """
#     cache = {}
# 
#     @wraps(klass, assigned=("__name__", "__module__"), updated=())
#     class _decorated(klass):
#         # The wraps decorator can't do this because __doc__
#         # isn't writable once the class is created
#         __doc__ = klass.__doc__
# 
#         def __new__(cls, *args, **kwargs):
#             """
#             Pass through...
#             :param args:
#             :param kwargs:
#             :return:
#             """
#             key = (cls,) + args + tuple(kwargs.items())
#             try:
#                 inst = cache.get(key, None)
#             except TypeError:
#                 # Can't cache this set of arguments
#                 inst = key = None
#             if inst is None:
#                 # Technically this is cheating, but it works,
#                 # and takes care of initializing the instance
#                 # (so we can override __init__ below safely);
#                 # calling up to klass.__new__ would be the
#                 # "official" way to create the instance, but
#                 # that raises DeprecationWarning if there are
#                 # args or kwargs and klass does not override
#                 # __new__ (which most classes don't), because
#                 # object.__new__ takes no parameters (and in
#                 # Python 3 the warning will become an error)
#                 inst = klass(*args, **kwargs)
#                 # This makes isinstance and issubclass work
#                 # properly
#                 inst.__class__ = cls
#                 if key is not None:
#                     cache[key] = inst
#             return inst
# 
#         def __init__(self, *args, **kwargs):
#             # This will be called every time __new__ is
#             # called, so we skip initializing here and do
#             # it only when the instance is created above
#             pass
# 
#     return _decorated


# Path: tests/test_design_patterns.py
import unittest
import pickle
from monty.design_patterns import singleton, cached_class

@cached_class
class A:
    def __init__(self, val):
        self.val = val

    def __eq__(self, other):
        return self.val == other.val

    def __getinitargs__(self):
        return (self.val,)

    def __getnewargs__(self):
        return (self.val,)


class CachedClassTest(unittest.TestCase):
    def test_cached_class(self):
        a1a = A(1)
        a1b = A(1)
        a2 = A(2)

        self.assertEqual(id(a1a), id(a1b))
        self.assertNotEqual(id(a1a), id(a2))

    # def test_pickle(self):
    #     a = A(2)
    #     o = pickle.dumps(a)
    #     self.assertEqual(a, pickle.loads(o))



if __name__ == "__main__":