base_obj.py 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2021 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. base_obj.py: base objects with no internal imports for the MMGen suite
  20. """
  21. class AttrCtrl:
  22. """
  23. After instance is locked, forbid setting any attribute if the attribute is not present
  24. in either the class or instance dict.
  25. Ensure that attribute's type matches that of the instance attribute, or the class
  26. attribute, if _use_class_attr is True. If the instance or class attribute is set
  27. to None, no type checking is performed.
  28. """
  29. _lock = False
  30. _use_class_attr = False
  31. _skip_type_check = ()
  32. def lock(self):
  33. self._lock = True
  34. def __setattr__(self,name,value):
  35. if self._lock:
  36. def do_error(name,value,ref_val):
  37. raise AttributeError(
  38. f'{value!r}: invalid value for attribute {name!r}'
  39. + ' of {} object (must be of type {}, not {})'.format(
  40. type(self).__name__,
  41. type(ref_val).__name__,
  42. type(value).__name__ ) )
  43. if not hasattr(self,name):
  44. raise AttributeError(f'{type(self).__name__} object has no attribute {name!r}')
  45. ref_val = getattr(type(self),name) if self._use_class_attr else getattr(self,name)
  46. if (
  47. (name not in self._skip_type_check)
  48. and (ref_val is not None)
  49. and not isinstance(value,type(ref_val))
  50. ):
  51. do_error(name,value,ref_val)
  52. return object.__setattr__(self,name,value)
  53. def __delattr__(self,name,value):
  54. raise AttributeError('attribute cannot be deleted')
  55. class Lockable(AttrCtrl):
  56. """
  57. After instance is locked, its attributes become read-only, with the following exceptions:
  58. - if an attribute's name is in _set_ok, it can be set once after locking, if unset
  59. - if an attribute's name is in _reset_ok, read-only restrictions are bypassed and only
  60. AttrCtrl checking is performed
  61. An attribute is considered unset if its value is None, or if it evaluates to False but is
  62. not zero; or if it is not present in the instance __dict__ when _use_class_attr is True.
  63. """
  64. _set_ok = ()
  65. _reset_ok = ()
  66. def __setattr__(self,name,value):
  67. if self._lock and hasattr(self,name):
  68. val = getattr(self,name)
  69. if name not in (self._set_ok + self._reset_ok):
  70. raise AttributeError(f'attribute {name!r} of {type(self).__name__} object is read-only')
  71. elif name not in self._reset_ok:
  72. #print(self.__dict__)
  73. if not (
  74. val is None or (not (val == 0) and not val) or
  75. ( self._use_class_attr and name not in self.__dict__ ) ):
  76. raise AttributeError(
  77. f'attribute {name!r} of {type(self).__name__} object is already set,'
  78. + ' and resetting is forbidden' )
  79. # else name is in (_set_ok + _reset_ok) -- allow name to be in both lists
  80. return AttrCtrl.__setattr__(self,name,value)