Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pyrsistent/_precord.py: 30%
87 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 CheckedType, _restore_pickle, InvariantException, 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._pmap import PMap, pmap
8class _PRecordMeta(type):
9 def __new__(mcs, name, bases, dct):
10 set_fields(dct, bases, name='_precord_fields')
11 store_invariants(dct, bases, '_precord_invariants', '__invariant__')
13 dct['_precord_mandatory_fields'] = \
14 set(name for name, field in dct['_precord_fields'].items() if field.mandatory)
16 dct['_precord_initial_values'] = \
17 dict((k, field.initial) for k, field in dct['_precord_fields'].items() if field.initial is not PFIELD_NO_INITIAL)
20 dct['__slots__'] = ()
22 return super(_PRecordMeta, mcs).__new__(mcs, name, bases, dct)
25class PRecord(PMap, CheckedType, metaclass=_PRecordMeta):
26 """
27 A PRecord is a PMap with a fixed set of specified fields. Records are declared as python classes inheriting
28 from PRecord. Because it is a PMap it has full support for all Mapping methods such as iteration and element
29 access using subscript notation.
31 More documentation and examples of PRecord usage is available at https://github.com/tobgu/pyrsistent
32 """
33 def __new__(cls, **kwargs):
34 # Hack total! If these two special attributes exist that means we can create
35 # ourselves. Otherwise we need to go through the Evolver to create the structures
36 # for us.
37 if '_precord_size' in kwargs and '_precord_buckets' in kwargs:
38 return super(PRecord, cls).__new__(cls, kwargs['_precord_size'], kwargs['_precord_buckets'])
40 factory_fields = kwargs.pop('_factory_fields', None)
41 ignore_extra = kwargs.pop('_ignore_extra', False)
43 initial_values = kwargs
44 if cls._precord_initial_values:
45 initial_values = dict((k, v() if callable(v) else v)
46 for k, v in cls._precord_initial_values.items())
47 initial_values.update(kwargs)
49 e = _PRecordEvolver(cls, pmap(pre_size=len(cls._precord_fields)), _factory_fields=factory_fields, _ignore_extra=ignore_extra)
50 for k, v in initial_values.items():
51 e[k] = v
53 return e.persistent()
55 def set(self, *args, **kwargs):
56 """
57 Set a field in the record. This set function differs slightly from that in the PMap
58 class. First of all it accepts key-value pairs. Second it accepts multiple key-value
59 pairs to perform one, atomic, update of multiple fields.
60 """
62 # The PRecord set() can accept kwargs since all fields that have been declared are
63 # valid python identifiers. Also allow multiple fields to be set in one operation.
64 if args:
65 return super(PRecord, self).set(args[0], args[1])
67 return self.update(kwargs)
69 def evolver(self):
70 """
71 Returns an evolver of this object.
72 """
73 return _PRecordEvolver(self.__class__, self)
75 def __repr__(self):
76 return "{0}({1})".format(self.__class__.__name__,
77 ', '.join('{0}={1}'.format(k, repr(v)) for k, v in self.items()))
79 @classmethod
80 def create(cls, kwargs, _factory_fields=None, ignore_extra=False):
81 """
82 Factory method. Will create a new PRecord of the current type and assign the values
83 specified in kwargs.
85 :param ignore_extra: A boolean which when set to True will ignore any keys which appear in kwargs that are not
86 in the set of fields on the PRecord.
87 """
88 if isinstance(kwargs, cls):
89 return kwargs
91 if ignore_extra:
92 kwargs = {k: kwargs[k] for k in cls._precord_fields if k in kwargs}
94 return cls(_factory_fields=_factory_fields, _ignore_extra=ignore_extra, **kwargs)
96 def __reduce__(self):
97 # Pickling support
98 return _restore_pickle, (self.__class__, dict(self),)
100 def serialize(self, format=None):
101 """
102 Serialize the current PRecord using custom serializer functions for fields where
103 such have been supplied.
104 """
105 return dict((k, serialize(self._precord_fields[k].serializer, format, v)) for k, v in self.items())
108class _PRecordEvolver(PMap._Evolver):
109 __slots__ = ('_destination_cls', '_invariant_error_codes', '_missing_fields', '_factory_fields', '_ignore_extra')
111 def __init__(self, cls, original_pmap, _factory_fields=None, _ignore_extra=False):
112 super(_PRecordEvolver, self).__init__(original_pmap)
113 self._destination_cls = cls
114 self._invariant_error_codes = []
115 self._missing_fields = []
116 self._factory_fields = _factory_fields
117 self._ignore_extra = _ignore_extra
119 def __setitem__(self, key, original_value):
120 self.set(key, original_value)
122 def set(self, key, original_value):
123 field = self._destination_cls._precord_fields.get(key)
124 if field:
125 if self._factory_fields is None or field in self._factory_fields:
126 try:
127 if is_field_ignore_extra_complaint(PRecord, field, self._ignore_extra):
128 value = field.factory(original_value, ignore_extra=self._ignore_extra)
129 else:
130 value = field.factory(original_value)
131 except InvariantException as e:
132 self._invariant_error_codes += e.invariant_errors
133 self._missing_fields += e.missing_fields
134 return self
135 else:
136 value = original_value
138 check_type(self._destination_cls, field, key, value)
140 is_ok, error_code = field.invariant(value)
141 if not is_ok:
142 self._invariant_error_codes.append(error_code)
144 return super(_PRecordEvolver, self).set(key, value)
145 else:
146 raise AttributeError("'{0}' is not among the specified fields for {1}".format(key, self._destination_cls.__name__))
148 def persistent(self):
149 cls = self._destination_cls
150 is_dirty = self.is_dirty()
151 pm = super(_PRecordEvolver, self).persistent()
152 if is_dirty or not isinstance(pm, cls):
153 result = cls(_precord_buckets=pm._buckets, _precord_size=pm._size)
154 else:
155 result = pm
157 if cls._precord_mandatory_fields:
158 self._missing_fields += tuple('{0}.{1}'.format(cls.__name__, f) for f
159 in (cls._precord_mandatory_fields - set(result.keys())))
161 if self._invariant_error_codes or self._missing_fields:
162 raise InvariantException(tuple(self._invariant_error_codes), tuple(self._missing_fields),
163 'Field invariant failed')
165 check_global_invariants(result, cls._precord_invariants)
167 return result