base_obj.py 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2020 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. If _use_class_attr is True, ensure that attribute's type matches that of the class
  26. attribute, unless the class attribute is set to None, in which case no type checking
  27. is performed.
  28. """
  29. _lock = False
  30. _use_class_attr = False
  31. def lock(self):
  32. self._lock = True
  33. def __setattr__(self,name,value):
  34. if self._lock:
  35. def do_error(name,value,ref_val):
  36. raise AttributeError(
  37. f'{value!r}: invalid value for attribute {name!r}'
  38. + ' of {} object (must be of type {}, not {})'.format(
  39. type(self).__name__,
  40. type(ref_val).__name__,
  41. type(value).__name__ ) )
  42. if not hasattr(self,name):
  43. raise AttributeError(f'{type(self).__name__} object has no attribute {name!r}')
  44. ref_val = getattr(type(self),name) if self._use_class_attr else getattr(self,name)
  45. if (ref_val is not None) and not isinstance(value,type(ref_val)):
  46. do_error(name,value,ref_val)
  47. return object.__setattr__(self,name,value)
  48. def __delattr__(self,name,value):
  49. raise AttributeError('attribute cannot be deleted')
  50. class Lockable(AttrCtrl):
  51. """
  52. After instance is locked, its attributes become read-only, with the following exceptions:
  53. - if the attribute's name is in _set_ok, attr can be set once after locking, if unset
  54. - if the attribute's name is in _reset_ok, read-only restrictions are bypassed and only
  55. AttrCtrl checking is performed
  56. To determine whether an attribute is set, it's matched against either None or the class attribute,
  57. if _use_class_attr is True
  58. """
  59. _set_ok = ()
  60. _reset_ok = ()
  61. def __setattr__(self,name,value):
  62. if self._lock and hasattr(self,name):
  63. if name not in (self._set_ok + self._reset_ok):
  64. raise AttributeError(f'attribute {name!r} of {type(self).__name__} object is read-only')
  65. elif name not in self._reset_ok:
  66. #print(self.__dict__)
  67. if not (
  68. getattr(self,name) is None or
  69. ( self._use_class_attr and name not in self.__dict__ ) ):
  70. raise AttributeError(
  71. f'attribute {name!r} of {type(self).__name__} object is already set,'
  72. + ' and resetting is forbidden' )
  73. # name is in (_set_ok + _reset_ok) -- allow name to be in both lists
  74. return AttrCtrl.__setattr__(self,name,value)