Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of Patsy 

2# Copyright (C) 2011-2012 Nathaniel Smith <njs@pobox.com> 

3# See file LICENSE.txt for license information. 

4 

5# The core 'origin' tracking system. This point of this is to have machinery 

6# so if some object is ultimately derived from some portion of a string (e.g., 

7# a formula), then we can keep track of that, and use it to give proper error 

8# messages. 

9 

10# These are made available in the patsy.* namespace 

11__all__ = ["Origin"] 

12 

13class Origin(object): 

14 """This represents the origin of some object in some string. 

15 

16 For example, if we have an object ``x1_obj`` that was produced by parsing 

17 the ``x1`` in the formula ``"y ~ x1:x2"``, then we conventionally keep 

18 track of that relationship by doing:: 

19 

20 x1_obj.origin = Origin("y ~ x1:x2", 4, 6) 

21 

22 Then later if we run into a problem, we can do:: 

23 

24 raise PatsyError("invalid factor", x1_obj) 

25 

26 and we'll produce a nice error message like:: 

27 

28 PatsyError: invalid factor 

29 y ~ x1:x2 

30 ^^ 

31 

32 Origins are compared by value, and hashable. 

33 """ 

34 

35 def __init__(self, code, start, end): 

36 self.code = code 

37 self.start = start 

38 self.end = end 

39 

40 @classmethod 

41 def combine(cls, origin_objs): 

42 """Class method for combining a set of Origins into one large Origin 

43 that spans them. 

44 

45 Example usage: if we wanted to represent the origin of the "x1:x2" 

46 term, we could do ``Origin.combine([x1_obj, x2_obj])``. 

47 

48 Single argument is an iterable, and each element in the iterable 

49 should be either: 

50 

51 * An Origin object 

52 * ``None`` 

53 * An object that has a ``.origin`` attribute which fulfills the above 

54 criteria. 

55  

56 Returns either an Origin object, or None. 

57 """ 

58 origins = [] 

59 for obj in origin_objs: 

60 if obj is not None and not isinstance(obj, Origin): 

61 obj = obj.origin 

62 if obj is None: 

63 continue 

64 origins.append(obj) 

65 if not origins: 

66 return None 

67 codes = set([o.code for o in origins]) 

68 assert len(codes) == 1 

69 start = min([o.start for o in origins]) 

70 end = max([o.end for o in origins]) 

71 return cls(codes.pop(), start, end) 

72 

73 def relevant_code(self): 

74 """Extracts and returns the span of the original code represented by 

75 this Origin. Example: ``x1``.""" 

76 return self.code[self.start:self.end] 

77 

78 def __eq__(self, other): 

79 return (isinstance(other, Origin) 

80 and self.code == other.code 

81 and self.start == other.start 

82 and self.end == other.end) 

83 

84 def __ne__(self, other): 

85 return not self == other 

86 

87 def __hash__(self): 

88 return hash((Origin, self.code, self.start, self.end)) 

89 

90 def caretize(self, indent=0): 

91 """Produces a user-readable two line string indicating the origin of 

92 some code. Example:: 

93 

94 y ~ x1:x2 

95 ^^ 

96 

97 If optional argument 'indent' is given, then both lines will be 

98 indented by this much. The returned string does not have a trailing 

99 newline. 

100 """ 

101 return ("%s%s\n%s%s%s" 

102 % (" " * indent, 

103 self.code, 

104 " " * indent, 

105 " " * self.start, 

106 "^" * (self.end - self.start))) 

107 

108 def __repr__(self): 

109 return "<Origin %s->%s<-%s (%s-%s)>" % ( 

110 self.code[:self.start], 

111 self.code[self.start:self.end], 

112 self.code[self.end:], 

113 self.start, self.end) 

114 

115 # We reimplement patsy.util.no_pickling, to avoid circular import issues 

116 def __getstate__(self): 

117 raise NotImplementedError 

118 

119def test_Origin(): 

120 o1 = Origin("012345", 2, 4) 

121 o2 = Origin("012345", 4, 5) 

122 assert o1.caretize() == "012345\n ^^" 

123 assert o2.caretize() == "012345\n ^" 

124 o3 = Origin.combine([o1, o2]) 

125 assert o3.code == "012345" 

126 assert o3.start == 2 

127 assert o3.end == 5 

128 assert o3.caretize(indent=2) == " 012345\n ^^^" 

129 assert o3 == Origin("012345", 2, 5) 

130 

131 class ObjWithOrigin(object): 

132 def __init__(self, origin=None): 

133 self.origin = origin 

134 o4 = Origin.combine([ObjWithOrigin(o1), ObjWithOrigin(), None]) 

135 assert o4 == o1 

136 o5 = Origin.combine([ObjWithOrigin(o1), o2]) 

137 assert o5 == o3 

138 

139 assert Origin.combine([ObjWithOrigin(), ObjWithOrigin()]) is None 

140 

141 from patsy.util import assert_no_pickling 

142 assert_no_pickling(Origin("", 0, 0))