forked from pydata/patsy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathorigin.py
More file actions
142 lines (113 loc) · 4.48 KB
/
origin.py
File metadata and controls
142 lines (113 loc) · 4.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# This file is part of Patsy
# Copyright (C) 2011-2012 Nathaniel Smith <njs@pobox.com>
# See file LICENSE.txt for license information.
# The core 'origin' tracking system. This point of this is to have machinery
# so if some object is ultimately derived from some portion of a string (e.g.,
# a formula), then we can keep track of that, and use it to give proper error
# messages.
# These are made available in the patsy.* namespace
__all__ = ["Origin"]
class Origin(object):
"""This represents the origin of some object in some string.
For example, if we have an object ``x1_obj`` that was produced by parsing
the ``x1`` in the formula ``"y ~ x1:x2"``, then we conventionally keep
track of that relationship by doing::
x1_obj.origin = Origin("y ~ x1:x2", 4, 6)
Then later if we run into a problem, we can do::
raise PatsyError("invalid factor", x1_obj)
and we'll produce a nice error message like::
PatsyError: invalid factor
y ~ x1:x2
^^
Origins are compared by value, and hashable.
"""
def __init__(self, code, start, end):
self.code = code
self.start = start
self.end = end
@classmethod
def combine(cls, origin_objs):
"""Class method for combining a set of Origins into one large Origin
that spans them.
Example usage: if we wanted to represent the origin of the "x1:x2"
term, we could do ``Origin.combine([x1_obj, x2_obj])``.
Single argument is an iterable, and each element in the iterable
should be either:
* An Origin object
* ``None``
* An object that has a ``.origin`` attribute which fulfills the above
criteria.
Returns either an Origin object, or None.
"""
origins = []
for obj in origin_objs:
if obj is not None and not isinstance(obj, Origin):
obj = obj.origin
if obj is None:
continue
origins.append(obj)
if not origins:
return None
codes = set([o.code for o in origins])
assert len(codes) == 1
start = min([o.start for o in origins])
end = max([o.end for o in origins])
return cls(codes.pop(), start, end)
def relevant_code(self):
"""Extracts and returns the span of the original code represented by
this Origin. Example: ``x1``."""
return self.code[self.start:self.end]
def __eq__(self, other):
return (isinstance(other, Origin)
and self.code == other.code
and self.start == other.start
and self.end == other.end)
def __ne__(self, other):
return not self == other
def __hash__(self):
return hash((Origin, self.code, self.start, self.end))
def caretize(self, indent=0):
"""Produces a user-readable two line string indicating the origin of
some code. Example::
y ~ x1:x2
^^
If optional argument 'indent' is given, then both lines will be
indented by this much. The returned string does not have a trailing
newline.
"""
return ("%s%s\n%s%s%s"
% (" " * indent,
self.code,
" " * indent,
" " * self.start,
"^" * (self.end - self.start)))
def __repr__(self):
return "<Origin %s->%s<-%s (%s-%s)>" % (
self.code[:self.start],
self.code[self.start:self.end],
self.code[self.end:],
self.start, self.end)
# We reimplement patsy.util.no_pickling, to avoid circular import issues
def __getstate__(self):
raise NotImplementedError
def test_Origin():
o1 = Origin("012345", 2, 4)
o2 = Origin("012345", 4, 5)
assert o1.caretize() == "012345\n ^^"
assert o2.caretize() == "012345\n ^"
o3 = Origin.combine([o1, o2])
assert o3.code == "012345"
assert o3.start == 2
assert o3.end == 5
assert o3.caretize(indent=2) == " 012345\n ^^^"
assert o3 == Origin("012345", 2, 5)
class ObjWithOrigin(object):
def __init__(self, origin=None):
self.origin = origin
o4 = Origin.combine([ObjWithOrigin(o1), ObjWithOrigin(), None])
assert o4 == o1
o5 = Origin.combine([ObjWithOrigin(o1), o2])
assert o5 == o3
assert Origin.combine([ObjWithOrigin(), ObjWithOrigin()]) is None
from patsy.util import assert_no_pickling
assert_no_pickling(Origin("", 0, 0))