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
« 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
8def _is_pclass(bases):
9 return len(bases) == 1 and bases[0] == CheckedType
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'])
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__',)
23 return super(PClassMeta, mcs).__new__(mcs, name, bases, dct)
25_MISSING_VALUE = object()
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)
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.
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))
70 if invariant_errors or missing_fields:
71 raise InvariantException(tuple(invariant_errors), tuple(missing_fields), 'Field invariant failed')
73 if kwargs:
74 raise AttributeError("'{0}' are not among the specified fields for {1}".format(
75 ', '.join(kwargs), cls.__name__))
77 check_global_invariants(result, cls._pclass_invariants)
79 result._pclass_frozen = True
80 return result
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.
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]
104 factory_fields = set(kwargs)
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
112 return self.__class__(_factory_fields=factory_fields, **kwargs)
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.
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
126 if ignore_extra:
127 kwargs = {k: kwargs[k] for k in cls._pclass_fields if k in kwargs}
129 return cls(_factory_fields=_factory_fields, ignore_extra=ignore_extra, **kwargs)
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)
142 return result
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)
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
159 return True
161 return NotImplemented
163 def __ne__(self, other):
164 return not self == other
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))
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))
174 super(PClass, self).__setattr__(key, value)
176 def __delattr__(self, key):
177 raise AttributeError("Can't delete attribute, key={0}, use remove()".format(key))
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
186 return result
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()))
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,)
197 def evolver(self):
198 """
199 Returns an evolver for this object.
200 """
201 return _PClassEvolver(self, self._to_dict())
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()
213class _PClassEvolver(object):
214 __slots__ = ('_pclass_evolver_original', '_pclass_evolver_data', '_pclass_evolver_data_is_dirty', '_factory_fields')
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()
222 def __getitem__(self, item):
223 return self._pclass_evolver_data[item]
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
231 return self
233 def __setitem__(self, key, value):
234 self.set(key, value)
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
243 raise AttributeError(item)
245 def __delitem__(self, item):
246 self.remove(item)
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)
253 return self._pclass_evolver_original
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)
261 def __getattr__(self, item):
262 return self[item]