import copy,functools

def immutable(clazz):
	"""
	author:	Martin Robinson
	email:	saloparenator@gmail.com
	project:Immutable
	about:
	Immutable decorator
	use on class declaration
	will prevent any mutation on the class
	event with method
	"""
	class immutableClazz(object):
		def __init__(self,*args, **kwargs):
			super(immutableClazz, self).__setattr__('mutable', clazz(*args, **kwargs))

		def __getattr__(self,*args,**kwargs):
			return getattr(self.mutable,*args,**kwargs)
			
		def __setattr__(self,*args,**kwargs):
			raise TypeError("can't modify immutable instance")
			
		def __delattr__(self,*args,**kwargs):
			raise TypeError("can't modify immutable instance")

        def __repr__(self):
            return self.mutable.__repr__()
        
        def __str__(self):
            return self.mutable.__str__()
	return immutableClazz

def immutator(func):
	"""
	author:	Martin Robinson
	email:	saloparenator@gmail.com
	project:Immutable
	about:
	Immutator method decorator
	the method will not receive self but a shallow copy of the object
	then the methode can freely change any field
	whatever your method return, the decorator will return the copy
	your methode become a fluent immutator that return modifed copy
	the object copy is immutable as well
	"""
	class immutableClazz(object):
		def __init__(self,obj):
			super(immutableClazz, self).__setattr__('mutable', obj)

		def __getattr__(self,*args,**kwargs):
			return getattr(self.mutable,*args,**kwargs)
			
		def __setattr__(self,*args,**kwargs):
			raise TypeError("can't modify immutable instance")
			
		def __delattr__(self,*args,**kwargs):
			raise TypeError("can't modify immutable instance")

        def __repr__(self):
            return self.mutable.__repr__()
        
        def __str__(self):
            return self.mutable.__str__()
	@functools.wraps(func)
	def closure(self,*args, **kwargs):
		clone = copy.copy(self)
		func(clone,*args, **kwargs)
		return immutableClazz(clone)
	return closure

if __name__=='__main__':

	@immutable
	class Obj(object):
		def __init__(self, val):
			self.val = val
		@immutator
		def change(copy,val):
			copy.val=val
			return copy

	
	print 'initialise immutable object with 5'
	obj = Obj(5)
	print 'immutable object val contain',obj.val
	print 'use immutator with 6'
	nuobj = obj.change(6)
	print 'immutable object still contain',obj.val
	print 'new object got',nuobj.val

	print 'try to overide val in new object'
	try:
		nuobj.val=7
		print 'immutability fail'
	except:
		print 'immutability succes'
	print 'try to overide val in immutable object'
	try:
		obj.val=7
		print 'immutability fail'
	except:
		print 'immutability succes'
