Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pyrsistent/_immutable.py: 10%
20 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
1import sys
4def immutable(members='', name='Immutable', verbose=False):
5 """
6 Produces a class that either can be used standalone or as a base class for persistent classes.
8 This is a thin wrapper around a named tuple.
10 Constructing a type and using it to instantiate objects:
12 >>> Point = immutable('x, y', name='Point')
13 >>> p = Point(1, 2)
14 >>> p2 = p.set(x=3)
15 >>> p
16 Point(x=1, y=2)
17 >>> p2
18 Point(x=3, y=2)
20 Inheriting from a constructed type. In this case no type name needs to be supplied:
22 >>> class PositivePoint(immutable('x, y')):
23 ... __slots__ = tuple()
24 ... def __new__(cls, x, y):
25 ... if x > 0 and y > 0:
26 ... return super(PositivePoint, cls).__new__(cls, x, y)
27 ... raise Exception('Coordinates must be positive!')
28 ...
29 >>> p = PositivePoint(1, 2)
30 >>> p.set(x=3)
31 PositivePoint(x=3, y=2)
32 >>> p.set(y=-3)
33 Traceback (most recent call last):
34 Exception: Coordinates must be positive!
36 The persistent class also supports the notion of frozen members. The value of a frozen member
37 cannot be updated. For example it could be used to implement an ID that should remain the same
38 over time. A frozen member is denoted by a trailing underscore.
40 >>> Point = immutable('x, y, id_', name='Point')
41 >>> p = Point(1, 2, id_=17)
42 >>> p.set(x=3)
43 Point(x=3, y=2, id_=17)
44 >>> p.set(id_=18)
45 Traceback (most recent call last):
46 AttributeError: Cannot set frozen members id_
47 """
49 if isinstance(members, str):
50 members = members.replace(',', ' ').split()
52 def frozen_member_test():
53 frozen_members = ["'%s'" % f for f in members if f.endswith('_')]
54 if frozen_members:
55 return """
56 frozen_fields = fields_to_modify & set([{frozen_members}])
57 if frozen_fields:
58 raise AttributeError('Cannot set frozen members %s' % ', '.join(frozen_fields))
59 """.format(frozen_members=', '.join(frozen_members))
61 return ''
63 quoted_members = ', '.join("'%s'" % m for m in members)
64 template = """
65class {class_name}(namedtuple('ImmutableBase', [{quoted_members}])):
66 __slots__ = tuple()
68 def __repr__(self):
69 return super({class_name}, self).__repr__().replace('ImmutableBase', self.__class__.__name__)
71 def set(self, **kwargs):
72 if not kwargs:
73 return self
75 fields_to_modify = set(kwargs.keys())
76 if not fields_to_modify <= {member_set}:
77 raise AttributeError("'%s' is not a member" % ', '.join(fields_to_modify - {member_set}))
79 {frozen_member_test}
81 return self.__class__.__new__(self.__class__, *map(kwargs.pop, [{quoted_members}], self))
82""".format(quoted_members=quoted_members,
83 member_set="set([%s])" % quoted_members if quoted_members else 'set()',
84 frozen_member_test=frozen_member_test(),
85 class_name=name)
87 if verbose:
88 print(template)
90 from collections import namedtuple
91 namespace = dict(namedtuple=namedtuple, __name__='pyrsistent_immutable')
92 try:
93 exec(template, namespace)
94 except SyntaxError as e:
95 raise SyntaxError(str(e) + ':\n' + template) from e
97 return namespace[name]