base_obj.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2024 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: base objects with no internal imports for the MMGen suite
  20. """
  21. class AsyncInit(type):
  22. async def __call__(cls,*args,**kwargs):
  23. instance = cls.__new__(cls,*args,**kwargs)
  24. await type(instance).__init__(instance,*args,**kwargs)
  25. return instance
  26. class AttrCtrlMeta(type):
  27. def __call__(cls,*args,**kwargs):
  28. instance = super().__call__(*args,**kwargs)
  29. if instance._autolock:
  30. instance._lock()
  31. return instance
  32. class AttrCtrl(metaclass=AttrCtrlMeta):
  33. """
  34. After instance is locked, forbid setting any attribute if the attribute is not present
  35. in either the class or instance dict.
  36. Ensure that attribute's type matches that of the instance attribute, or the class
  37. attribute, if _use_class_attr is True. If the instance or class attribute is set
  38. to None, no type checking is performed.
  39. If _default_to_none is True, return None when accessing non-existent attributes
  40. instead of raising AttributeError.
  41. """
  42. _autolock = True
  43. _locked = False
  44. _use_class_attr = False
  45. _default_to_none = False
  46. _skip_type_check = ()
  47. def _lock(self):
  48. self._locked = True
  49. def __getattr__(self,name):
  50. if self._locked and self._default_to_none:
  51. return None
  52. else:
  53. raise AttributeError(f'{type(self).__name__} object has no attribute {name!r}')
  54. def __setattr__(self,name,value):
  55. if self._locked:
  56. assert name != '_locked', 'lock can be set only once'
  57. def do_error(name,value,ref_val):
  58. raise AttributeError(
  59. f'{value!r}: invalid value for attribute {name!r}'
  60. + ' of {} object (must be of type {}, not {})'.format(
  61. type(self).__name__,
  62. type(ref_val).__name__,
  63. type(value).__name__ ) )
  64. if not (name in self.__dict__ or hasattr(type(self),name)):
  65. raise AttributeError(f'{type(self).__name__} object has no attribute {name!r}')
  66. ref_val = getattr(type(self),name) if self._use_class_attr else getattr(self,name)
  67. if (
  68. (name not in self._skip_type_check)
  69. and (ref_val is not None)
  70. and not isinstance(value,type(ref_val))
  71. ):
  72. do_error(name,value,ref_val)
  73. return object.__setattr__(self,name,value)
  74. def __delattr__(self,name):
  75. if self._locked:
  76. raise AttributeError('attribute cannot be deleted')
  77. return object.__delattr__(self,name)
  78. class Lockable(AttrCtrl):
  79. """
  80. After instance is locked, its attributes become read-only, with the following exceptions:
  81. - if an attribute's name is in _set_ok, it can be set once after locking, if unset
  82. - if an attribute's name is in _reset_ok, read-only restrictions are bypassed and only
  83. AttrCtrl checking is performed
  84. An attribute is considered unset if its value is None, or if it evaluates to False but is
  85. not zero; or if it is not present in the instance __dict__ when _use_class_attr is True.
  86. """
  87. _set_ok = ()
  88. _reset_ok = ()
  89. def _lock(self):
  90. for name in ('_set_ok','_reset_ok'):
  91. for attr in getattr(self,name):
  92. assert hasattr(self,attr), (
  93. f'attribute {attr!r} in {name!r} not found in {type(self).__name__} object {id(self)}' )
  94. super()._lock()
  95. def __setattr__(self,name,value):
  96. if self._locked and (name in self.__dict__ or hasattr(type(self),name)):
  97. val = getattr(self,name)
  98. if name not in (self._set_ok + self._reset_ok):
  99. raise AttributeError(f'attribute {name!r} of {type(self).__name__} object is read-only')
  100. elif name not in self._reset_ok:
  101. #print(self.__dict__)
  102. if not (
  103. (val != 0 and not val) or
  104. (self._use_class_attr and name not in self.__dict__) ):
  105. raise AttributeError(
  106. f'attribute {name!r} of {type(self).__name__} object is already set,'
  107. + ' and resetting is forbidden' )
  108. # else name is in (_set_ok + _reset_ok) -- allow name to be in both lists
  109. return AttrCtrl.__setattr__(self,name,value)