item.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. from .constants import ITEM_KEYS
  2. from .helpers import safe_encode, safe_decode, bigint_to_int, int_to_bigint, StableDict
  3. class PropDict:
  4. """
  5. Manage a dictionary via properties.
  6. - initialization by giving a dict or kw args
  7. - on initialization, normalize dict keys to be str type
  8. - access dict via properties, like: x.key_name
  9. - membership check via: 'key_name' in x
  10. - optionally, encode when setting a value
  11. - optionally, decode when getting a value
  12. - be safe against typos in key names: check against VALID_KEYS
  13. - when setting a value: check type of value
  14. """
  15. VALID_KEYS = None # override with <set of str> in child class
  16. def __init__(self, data_dict=None, **kw):
  17. if data_dict is None:
  18. data = kw
  19. elif not isinstance(data_dict, dict):
  20. raise TypeError("data_dict must be dict")
  21. else:
  22. data = data_dict
  23. # internally, we want an dict with only str-typed keys
  24. _dict = {}
  25. for k, v in data.items():
  26. if isinstance(k, bytes):
  27. k = k.decode()
  28. elif not isinstance(k, str):
  29. raise TypeError("dict keys must be str or bytes, not %r" % k)
  30. _dict[k] = v
  31. unknown_keys = set(_dict) - self.VALID_KEYS
  32. if unknown_keys:
  33. raise ValueError("dict contains unknown keys %s" % ','.join(unknown_keys))
  34. self._dict = _dict
  35. def as_dict(self):
  36. """return the internal dictionary"""
  37. return self._dict # XXX use StableDict?
  38. def _check_key(self, key):
  39. """make sure key is of type str and known"""
  40. if not isinstance(key, str):
  41. raise TypeError("key must be str")
  42. if key not in self.VALID_KEYS:
  43. raise ValueError("key '%s' is not a valid key" % key)
  44. return key
  45. def __contains__(self, key):
  46. """do we have this key?"""
  47. return self._check_key(key) in self._dict
  48. def get(self, key, default=None):
  49. """get value for key, return default if key does not exist"""
  50. return getattr(self, self._check_key(key), default)
  51. @staticmethod
  52. def _make_property(key, value_type, value_type_name=None, encode=None, decode=None):
  53. """return a property that deals with self._dict[key]"""
  54. assert isinstance(key, str)
  55. if value_type_name is None:
  56. value_type_name = value_type.__name__
  57. doc = "%s (%s)" % (key, value_type_name)
  58. type_error_msg = "%s value must be %s" % (key, value_type_name)
  59. attr_error_msg = "attribute %s not found" % key
  60. def _get(self):
  61. try:
  62. value = self._dict[key]
  63. except KeyError:
  64. raise AttributeError(attr_error_msg) from None
  65. if decode is not None:
  66. value = decode(value)
  67. return value
  68. def _set(self, value):
  69. if not isinstance(value, value_type):
  70. raise TypeError(type_error_msg)
  71. if encode is not None:
  72. value = encode(value)
  73. self._dict[key] = value
  74. def _del(self):
  75. try:
  76. del self._dict[key]
  77. except KeyError:
  78. raise AttributeError(attr_error_msg) from None
  79. return property(_get, _set, _del, doc=doc)
  80. class Item(PropDict):
  81. """
  82. Item abstraction that deals with validation and the low-level details internally:
  83. Items are created either from msgpack unpacker output, from another dict, from kwargs or
  84. built step-by-step by setting attributes.
  85. msgpack gives us a dict with bytes-typed keys, just give it to Item(d) and use item.key_name later.
  86. msgpack gives us byte-typed values for stuff that should be str, we automatically decode when getting
  87. such a property and encode when setting it.
  88. If an Item shall be serialized, give as_dict() method output to msgpack packer.
  89. """
  90. VALID_KEYS = set(key.decode() for key in ITEM_KEYS) # we want str-typed keys
  91. # properties statically defined, so that IDEs can know their names:
  92. path = PropDict._make_property('path', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
  93. source = PropDict._make_property('source', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
  94. acl_access = PropDict._make_property('acl_access', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
  95. acl_default = PropDict._make_property('acl_default', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
  96. acl_extended = PropDict._make_property('acl_extended', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
  97. acl_nfs4 = PropDict._make_property('acl_nfs4', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
  98. user = PropDict._make_property('user', (str, type(None)), 'surrogate-escaped str or None', encode=safe_encode, decode=safe_decode)
  99. group = PropDict._make_property('group', (str, type(None)), 'surrogate-escaped str or None', encode=safe_encode, decode=safe_decode)
  100. mode = PropDict._make_property('mode', int)
  101. uid = PropDict._make_property('uid', int)
  102. gid = PropDict._make_property('gid', int)
  103. rdev = PropDict._make_property('rdev', int)
  104. bsdflags = PropDict._make_property('bsdflags', int)
  105. atime = PropDict._make_property('atime', int, 'bigint', encode=int_to_bigint, decode=bigint_to_int)
  106. ctime = PropDict._make_property('ctime', int, 'bigint', encode=int_to_bigint, decode=bigint_to_int)
  107. mtime = PropDict._make_property('mtime', int, 'bigint', encode=int_to_bigint, decode=bigint_to_int)
  108. hardlink_master = PropDict._make_property('hardlink_master', bool)
  109. chunks = PropDict._make_property('chunks', list)
  110. xattrs = PropDict._make_property('xattrs', StableDict)