Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pyrsistent/_pclass.py: 30%

145 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-01 06:54 +0000

1from pyrsistent._checked_types import (InvariantException, CheckedType, _restore_pickle, store_invariants) 

2from pyrsistent._field_common import ( 

3 set_fields, check_type, is_field_ignore_extra_complaint, PFIELD_NO_INITIAL, serialize, check_global_invariants 

4) 

5from pyrsistent._transformations import transform 

6 

7 

8def _is_pclass(bases): 

9 return len(bases) == 1 and bases[0] == CheckedType 

10 

11 

12class PClassMeta(type): 

13 def __new__(mcs, name, bases, dct): 

14 set_fields(dct, bases, name='_pclass_fields') 

15 store_invariants(dct, bases, '_pclass_invariants', '__invariant__') 

16 dct['__slots__'] = ('_pclass_frozen',) + tuple(key for key in dct['_pclass_fields']) 

17 

18 # There must only be one __weakref__ entry in the inheritance hierarchy, 

19 # lets put it on the top level class. 

20 if _is_pclass(bases): 

21 dct['__slots__'] += ('__weakref__',) 

22 

23 return super(PClassMeta, mcs).__new__(mcs, name, bases, dct) 

24 

25_MISSING_VALUE = object() 

26 

27 

28def _check_and_set_attr(cls, field, name, value, result, invariant_errors): 

29 check_type(cls, field, name, value) 

30 is_ok, error_code = field.invariant(value) 

31 if not is_ok: 

32 invariant_errors.append(error_code) 

33 else: 

34 setattr(result, name, value) 

35 

36 

37class PClass(CheckedType, metaclass=PClassMeta): 

38 """ 

39 A PClass is a python class with a fixed set of specified fields. PClasses are declared as python classes inheriting 

40 from PClass. It is defined the same way that PRecords are and behaves like a PRecord in all aspects except that it 

41 is not a PMap and hence not a collection but rather a plain Python object. 

42 

43 

44 More documentation and examples of PClass usage is available at https://github.com/tobgu/pyrsistent 

45 """ 

46 def __new__(cls, **kwargs): # Support *args? 

47 result = super(PClass, cls).__new__(cls) 

48 factory_fields = kwargs.pop('_factory_fields', None) 

49 ignore_extra = kwargs.pop('ignore_extra', None) 

50 missing_fields = [] 

51 invariant_errors = [] 

52 for name, field in cls._pclass_fields.items(): 

53 if name in kwargs: 

54 if factory_fields is None or name in factory_fields: 

55 if is_field_ignore_extra_complaint(PClass, field, ignore_extra): 

56 value = field.factory(kwargs[name], ignore_extra=ignore_extra) 

57 else: 

58 value = field.factory(kwargs[name]) 

59 else: 

60 value = kwargs[name] 

61 _check_and_set_attr(cls, field, name, value, result, invariant_errors) 

62 del kwargs[name] 

63 elif field.initial is not PFIELD_NO_INITIAL: 

64 initial = field.initial() if callable(field.initial) else field.initial 

65 _check_and_set_attr( 

66 cls, field, name, initial, result, invariant_errors) 

67 elif field.mandatory: 

68 missing_fields.append('{0}.{1}'.format(cls.__name__, name)) 

69 

70 if invariant_errors or missing_fields: 

71 raise InvariantException(tuple(invariant_errors), tuple(missing_fields), 'Field invariant failed') 

72 

73 if kwargs: 

74 raise AttributeError("'{0}' are not among the specified fields for {1}".format( 

75 ', '.join(kwargs), cls.__name__)) 

76 

77 check_global_invariants(result, cls._pclass_invariants) 

78 

79 result._pclass_frozen = True 

80 return result 

81 

82 def set(self, *args, **kwargs): 

83 """ 

84 Set a field in the instance. Returns a new instance with the updated value. The original instance remains 

85 unmodified. Accepts key-value pairs or single string representing the field name and a value. 

86 

87 >>> from pyrsistent import PClass, field 

88 >>> class AClass(PClass): 

89 ... x = field() 

90 ... 

91 >>> a = AClass(x=1) 

92 >>> a2 = a.set(x=2) 

93 >>> a3 = a.set('x', 3) 

94 >>> a 

95 AClass(x=1) 

96 >>> a2 

97 AClass(x=2) 

98 >>> a3 

99 AClass(x=3) 

100 """ 

101 if args: 

102 kwargs[args[0]] = args[1] 

103 

104 factory_fields = set(kwargs) 

105 

106 for key in self._pclass_fields: 

107 if key not in kwargs: 

108 value = getattr(self, key, _MISSING_VALUE) 

109 if value is not _MISSING_VALUE: 

110 kwargs[key] = value 

111 

112 return self.__class__(_factory_fields=factory_fields, **kwargs) 

113 

114 @classmethod 

115 def create(cls, kwargs, _factory_fields=None, ignore_extra=False): 

116 """ 

117 Factory method. Will create a new PClass of the current type and assign the values 

118 specified in kwargs. 

119 

120 :param ignore_extra: A boolean which when set to True will ignore any keys which appear in kwargs that are not 

121 in the set of fields on the PClass. 

122 """ 

123 if isinstance(kwargs, cls): 

124 return kwargs 

125 

126 if ignore_extra: 

127 kwargs = {k: kwargs[k] for k in cls._pclass_fields if k in kwargs} 

128 

129 return cls(_factory_fields=_factory_fields, ignore_extra=ignore_extra, **kwargs) 

130 

131 def serialize(self, format=None): 

132 """ 

133 Serialize the current PClass using custom serializer functions for fields where 

134 such have been supplied. 

135 """ 

136 result = {} 

137 for name in self._pclass_fields: 

138 value = getattr(self, name, _MISSING_VALUE) 

139 if value is not _MISSING_VALUE: 

140 result[name] = serialize(self._pclass_fields[name].serializer, format, value) 

141 

142 return result 

143 

144 def transform(self, *transformations): 

145 """ 

146 Apply transformations to the currency PClass. For more details on transformations see 

147 the documentation for PMap. Transformations on PClasses do not support key matching 

148 since the PClass is not a collection. Apart from that the transformations available 

149 for other persistent types work as expected. 

150 """ 

151 return transform(self, transformations) 

152 

153 def __eq__(self, other): 

154 if isinstance(other, self.__class__): 

155 for name in self._pclass_fields: 

156 if getattr(self, name, _MISSING_VALUE) != getattr(other, name, _MISSING_VALUE): 

157 return False 

158 

159 return True 

160 

161 return NotImplemented 

162 

163 def __ne__(self, other): 

164 return not self == other 

165 

166 def __hash__(self): 

167 # May want to optimize this by caching the hash somehow 

168 return hash(tuple((key, getattr(self, key, _MISSING_VALUE)) for key in self._pclass_fields)) 

169 

170 def __setattr__(self, key, value): 

171 if getattr(self, '_pclass_frozen', False): 

172 raise AttributeError("Can't set attribute, key={0}, value={1}".format(key, value)) 

173 

174 super(PClass, self).__setattr__(key, value) 

175 

176 def __delattr__(self, key): 

177 raise AttributeError("Can't delete attribute, key={0}, use remove()".format(key)) 

178 

179 def _to_dict(self): 

180 result = {} 

181 for key in self._pclass_fields: 

182 value = getattr(self, key, _MISSING_VALUE) 

183 if value is not _MISSING_VALUE: 

184 result[key] = value 

185 

186 return result 

187 

188 def __repr__(self): 

189 return "{0}({1})".format(self.__class__.__name__, 

190 ', '.join('{0}={1}'.format(k, repr(v)) for k, v in self._to_dict().items())) 

191 

192 def __reduce__(self): 

193 # Pickling support 

194 data = dict((key, getattr(self, key)) for key in self._pclass_fields if hasattr(self, key)) 

195 return _restore_pickle, (self.__class__, data,) 

196 

197 def evolver(self): 

198 """ 

199 Returns an evolver for this object. 

200 """ 

201 return _PClassEvolver(self, self._to_dict()) 

202 

203 def remove(self, name): 

204 """ 

205 Remove attribute given by name from the current instance. Raises AttributeError if the 

206 attribute doesn't exist. 

207 """ 

208 evolver = self.evolver() 

209 del evolver[name] 

210 return evolver.persistent() 

211 

212 

213class _PClassEvolver(object): 

214 __slots__ = ('_pclass_evolver_original', '_pclass_evolver_data', '_pclass_evolver_data_is_dirty', '_factory_fields') 

215 

216 def __init__(self, original, initial_dict): 

217 self._pclass_evolver_original = original 

218 self._pclass_evolver_data = initial_dict 

219 self._pclass_evolver_data_is_dirty = False 

220 self._factory_fields = set() 

221 

222 def __getitem__(self, item): 

223 return self._pclass_evolver_data[item] 

224 

225 def set(self, key, value): 

226 if self._pclass_evolver_data.get(key, _MISSING_VALUE) is not value: 

227 self._pclass_evolver_data[key] = value 

228 self._factory_fields.add(key) 

229 self._pclass_evolver_data_is_dirty = True 

230 

231 return self 

232 

233 def __setitem__(self, key, value): 

234 self.set(key, value) 

235 

236 def remove(self, item): 

237 if item in self._pclass_evolver_data: 

238 del self._pclass_evolver_data[item] 

239 self._factory_fields.discard(item) 

240 self._pclass_evolver_data_is_dirty = True 

241 return self 

242 

243 raise AttributeError(item) 

244 

245 def __delitem__(self, item): 

246 self.remove(item) 

247 

248 def persistent(self): 

249 if self._pclass_evolver_data_is_dirty: 

250 return self._pclass_evolver_original.__class__(_factory_fields=self._factory_fields, 

251 **self._pclass_evolver_data) 

252 

253 return self._pclass_evolver_original 

254 

255 def __setattr__(self, key, value): 

256 if key not in self.__slots__: 

257 self.set(key, value) 

258 else: 

259 super(_PClassEvolver, self).__setattr__(key, value) 

260 

261 def __getattr__(self, item): 

262 return self[item]