base_obj.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  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 AttrCtrlMeta(type):
  22. def __call__(cls,*args,**kwargs):
  23. instance = super().__call__(*args,**kwargs)
  24. if instance._autolock:
  25. instance.lock()
  26. return instance
  27. class AttrCtrl(metaclass=AttrCtrlMeta):
  28. """
  29. After instance is locked, forbid setting any attribute if the attribute is not present
  30. in either the class or instance dict.
  31. Ensure that attribute's type matches that of the instance attribute, or the class
  32. attribute, if _use_class_attr is True. If the instance or class attribute is set
  33. to None, no type checking is performed.
  34. """
  35. _autolock = True
  36. _lock = False
  37. _use_class_attr = False
  38. _skip_type_check = ()
  39. def lock(self):
  40. self._lock = True
  41. def __setattr__(self,name,value):
  42. if self._lock:
  43. assert name != '_lock', 'lock can be set only once'
  44. def do_error(name,value,ref_val):
  45. raise AttributeError(
  46. f'{value!r}: invalid value for attribute {name!r}'
  47. + ' of {} object (must be of type {}, not {})'.format(
  48. type(self).__name__,
  49. type(ref_val).__name__,
  50. type(value).__name__ ) )
  51. if not hasattr(self,name):
  52. raise AttributeError(f'{type(self).__name__} object has no attribute {name!r}')
  53. ref_val = getattr(type(self),name) if self._use_class_attr else getattr(self,name)
  54. if (
  55. (name not in self._skip_type_check)
  56. and (ref_val is not None)
  57. and not isinstance(value,type(ref_val))
  58. ):
  59. do_error(name,value,ref_val)
  60. return object.__setattr__(self,name,value)
  61. def __delattr__(self,name,value):
  62. raise AttributeError('attribute cannot be deleted')
  63. class Lockable(AttrCtrl):
  64. """
  65. After instance is locked, its attributes become read-only, with the following exceptions:
  66. - if an attribute's name is in _set_ok, it can be set once after locking, if unset
  67. - if an attribute's name is in _reset_ok, read-only restrictions are bypassed and only
  68. AttrCtrl checking is performed
  69. An attribute is considered unset if its value is None, or if it evaluates to False but is
  70. not zero; or if it is not present in the instance __dict__ when _use_class_attr is True.
  71. """
  72. _set_ok = ()
  73. _reset_ok = ()
  74. def lock(self):
  75. for name in ('_set_ok','_reset_ok'):
  76. for attr in getattr(self,name):
  77. assert hasattr(self,attr), (
  78. f'attribute {attr!r} in {name!r} not found in {type(self).__name__} object {id(self)}' )
  79. super().lock()
  80. def __setattr__(self,name,value):
  81. if self._lock and hasattr(self,name):
  82. val = getattr(self,name)
  83. if name not in (self._set_ok + self._reset_ok):
  84. raise AttributeError(f'attribute {name!r} of {type(self).__name__} object is read-only')
  85. elif name not in self._reset_ok:
  86. #print(self.__dict__)
  87. if not (
  88. val is None or (not (val == 0) and not val) or
  89. ( self._use_class_attr and name not in self.__dict__ ) ):
  90. raise AttributeError(
  91. f'attribute {name!r} of {type(self).__name__} object is already set,'
  92. + ' and resetting is forbidden' )
  93. # else name is in (_set_ok + _reset_ok) -- allow name to be in both lists
  94. return AttrCtrl.__setattr__(self,name,value)