Debug Tools
lockable_type.py
1 #! /usr/bin/env python
2 # -*- coding: utf-8 -*-
3 
4 #
5 # Licensing
6 #
7 # Python Lockable Objects
8 # Copyright (C) 2018 Evandro Coan <https://github.com/evandrocoan>
9 #
10 # This program is free software; you can redistribute it and/or modify it
11 # under the terms of the GNU General Public License as published by the
12 # Free Software Foundation; either version 3 of the License, or ( at
13 # your option ) any later version.
14 #
15 # This program is distributed in the hope that it will be useful, but
16 # WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 # General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License
21 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #
23 
24 import os
25 import copy
26 
27 from debug_tools.utilities import get_unique_hash
28 from debug_tools.utilities import get_representation
29 
30 from debug_tools import getLogger
31 
32 # level 4 - Abstract Syntax Tree Parsing
33 log = getLogger( 127-4, __name__ )
34 
35 
36 class LockableType(object):
37  """
38  An object type which can have its attributes changes locked/blocked after its `lock()`
39  method being called.
40 
41  After locking, its string representation attribute is going to be saved as an attribute and
42  returned when needed.
43  """
44 
45  USE_STRING = True
46  EMQUOTE_STRING = False
47 
48  def __init__(self):
49  """
50  How to handle call to __setattr__ from __init__?
51  https://stackoverflow.com/questions/3870982/how-to-handle-call-to-setattr-from-init
52  """
53  super().__setattr__('locked', False)
54 
55 
56  self.locked = False
57 
58 
59  self.str = ""
60 
61 
62  self.len = 0
63 
64 
65  self._hash = get_unique_hash()
66 
67  self._original_len = self._len
68  self._original_str = self._str
69  self._original_hash = self._hash
70 
71  def __setattr__(self, name, value):
72  """
73  Block attributes from being changed after it is activated.
74  https://stackoverflow.com/questions/17020115/how-to-use-setattr-correctly-avoiding-infinite-recursion
75  """
76 
77  if self.locked:
78  raise AttributeError( "Attributes cannot be changed after `locked` is set to True! %s" % self.__repr__() )
79 
80  else:
81  super().__setattr__( name, value )
82 
83  def __eq__(self, other):
84  """
85  Determines whether this object is equal to another one based on their hashes.
86  """
87 
88  if isinstance( self, LockableType ) is isinstance( other, LockableType ):
89  return hash( self ) == hash( other )
90 
91  raise TypeError( "'=' not supported between instances of '%s' and '%s'" % (
92  self.__class__.__name__, other.__class__.__name__ ) )
93 
94  def __hash__(self):
95  """
96  Return the hash of this object based on its string representation.
97  """
98  return self._hash
99 
100  def repr(self):
101  """
102  See `__repr__()` for details.
103  """
104  return get_representation( self, ignore={'hash'}, emquote=self.EMQUOTE_STRING )
105 
106  def __repr__(self):
107  """
108  Prints a representation of this object within all its attributes.
109  """
110 
111  if self.USE_STRING:
112  return self.__str__()
113 
114  return self.repr()
115 
116  def __str__(self):
117  """
118  Python does not allow to dynamically/monkey patch its build in functions. Then, we create
119  out own function and call it from the built-in function.
120  """
121  return self._str()
122 
123  def _str(self):
124  """
125  Caches the string representation of this object, after locking its attributes changes with `lock()`
126  """
127 
128  if self.USE_STRING:
129  return super().__str__()
130 
131  return get_representation( self, ignore={'hash'}, emquote=self.EMQUOTE_STRING )
132 
133  def __len__(self):
134  """
135  Python does not allow to dynamically/monkey patch its build in functions. Then, we create
136  out own function and call it from the built-in function.
137  """
138  return self._len()
139 
140  def _len(self):
141  raise TypeError( "object of type '%s' has no len()" % self.__class__.__name__ )
142 
143  def _unlock(self):
144  """
145  Unblock the object changes allowing its attributes to be freely set.
146 
147  Do not call it if this object is inside a hashtable as list or set, because an object
148  cannot have its hash changed while they are inside it. Otherwise, the hashmap will not
149  get updated and the new version of the object will point as non existent.
150  """
151 
152  if not self.locked:
153  return
154 
155  self.__dict__['locked'] = False
156  self._len = self._original_len
157  self._str = self._original_str
158  self._hash = self._original_hash
159 
160  def lock(self):
161  """
162  Block further changes to this object attributes and cache its length and string
163  representation for faster access.
164  """
165 
166  if self.locked:
167  return
168 
169  self.str = str( self )
170  self._str = lambda: self.str
171 
172  self.len = len( self )
173  self._len = lambda: self.len
174 
175  self._hash = hash( self._str() )
176  self.locked = True
177 
178  def new(self, unlocked=True):
179  """
180  Creates and return a new copy of the current a object.
181  """
182  new_copy = copy.deepcopy( self )
183  # log( 1, "self: %s", self )
184  # log( 1, "new_copy: %s", new_copy )
185 
186  if unlocked:
187  new_copy._unlock()
188 
189  else:
190  new_copy.lock()
191 
192  return new_copy
193 
str
Caches the string representation of this object, after locking its attributes changes with lock() ...
_hash
An unique identifier for any LockableType object.
locked
Controls whether the attributes changes of this object are allow or not.
len
The caches the length of this object, used while this object is in locked state.
def __setattr__(self, name, value)