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

1import sys 

2 

3 

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. 

7 

8 This is a thin wrapper around a named tuple. 

9 

10 Constructing a type and using it to instantiate objects: 

11 

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) 

19 

20 Inheriting from a constructed type. In this case no type name needs to be supplied: 

21 

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! 

35 

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. 

39 

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 """ 

48 

49 if isinstance(members, str): 

50 members = members.replace(',', ' ').split() 

51 

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)) 

60 

61 return '' 

62 

63 quoted_members = ', '.join("'%s'" % m for m in members) 

64 template = """ 

65class {class_name}(namedtuple('ImmutableBase', [{quoted_members}])): 

66 __slots__ = tuple() 

67 

68 def __repr__(self): 

69 return super({class_name}, self).__repr__().replace('ImmutableBase', self.__class__.__name__) 

70 

71 def set(self, **kwargs): 

72 if not kwargs: 

73 return self 

74 

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})) 

78 

79 {frozen_member_test} 

80 

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) 

86 

87 if verbose: 

88 print(template) 

89 

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 

96 

97 return namespace[name]