This repository has been archived by the owner on Jan 30, 2020. It is now read-only.
/
constraints.py
156 lines (110 loc) · 4.32 KB
/
constraints.py
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import operator
import re
class BaseConstraint(object):
def __init__(self, name, value):
self.name = name
self.value = value
def resolve_value(self, item):
for attr in self.name.split('__'):
item = getattr(item, attr)
if item is None:
return None
if hasattr(item, '__call__'):
return item()
return item
def fits(self, item):
raise NotImplementedError
class ExactConstraint(BaseConstraint):
def fits(self, item):
return self.resolve_value(item) == self.value
class CaseInsensitiveExactConstraint(BaseConstraint):
def fits(self, item):
return self.resolve_value(item).lower() == self.value.lower()
class StartsWithConstraint(BaseConstraint):
def fits(self, item):
return self.resolve_value(item).startswith(self.value)
class CaseInsensitiveStartsWithConstraint(BaseConstraint):
def fits(self, item):
return self.resolve_value(item).lower().startswith(self.value.lower())
class EndsWithConstraint(BaseConstraint):
def fits(self, item):
return self.resolve_value(item).endswith(self.value)
class CaseInsensitiveEndsWithConstraint(BaseConstraint):
def fits(self, item):
return self.resolve_value(item).lower().endswith(self.value.lower())
class RegexConstraint(BaseConstraint):
def __init__(self, name, value):
super(RegexConstraint, self).__init__(name, value)
self.regex = re.compile(self.value)
def fits(self, item):
return self.regex.match(self.resolve_value(item))
class ContainsConstraint(BaseConstraint):
def fits(self, item):
return self.value in self.resolve_value(item)
class BaseComparativeConstraint(BaseConstraint):
def fits(self, item):
return self.COMPARATIVE_FUNCTION(self.resolve_value(item), self.value)
@property
def COMPARATIVE_FUNCTION(self):
raise NotImplementedError('Should be implemented in inherited class')
class GtConstraint(BaseComparativeConstraint):
COMPARATIVE_FUNCTION = operator.gt
class GteConstraint(BaseComparativeConstraint):
COMPARATIVE_FUNCTION = operator.ge
class LtConstraint(BaseComparativeConstraint):
COMPARATIVE_FUNCTION = operator.lt
class LteConstraint(BaseComparativeConstraint):
COMPARATIVE_FUNCTION = operator.le
class IsnullConstraint(BaseComparativeConstraint):
def fits(self, item):
return bool(self.resolve_value(item)) == self.value
class CountConstraint(BaseConstraint):
def fits(self, item):
return len(self.resolve_value(item)) == self.value
class CallableConstraint(object):
"""
We actually need this for just one single reason:
to get constraint-like interface for callable constraints.
So, this class isn't inherited from base constraint class
and isn't supported by factory.
It's separate, different constraint class
"""
def __init__(self, callable):
self.callable = callable
def fits(self, item):
return self.callable(item)
class ConstraintsFactory(object):
KEYWORD_SEPARATOR = '__'
DEFAULT_CONSTRAINT_CLASS = ExactConstraint
KEYWORD_TO_CONSTRAINT_CLASS_MAP = {
'exact': ExactConstraint,
'iexact': CaseInsensitiveExactConstraint,
'startswith': StartsWithConstraint,
'istartswith': CaseInsensitiveStartsWithConstraint,
'endswith': EndsWithConstraint,
'iendswith': CaseInsensitiveEndsWithConstraint,
'contains': ContainsConstraint,
'regex': RegexConstraint,
'gt': GtConstraint,
'gte': GteConstraint,
'lt': LtConstraint,
'lte': LteConstraint,
'isnull': IsnullConstraint,
'count': CountConstraint,
}
def __init__(self, name, value):
self.name = name
self.value = value
def get_constraint(self):
prefix, suffix = self.get_name_and_keyword(self.name)
if suffix and suffix in self.KEYWORD_TO_CONSTRAINT_CLASS_MAP:
name = prefix
cls = self.KEYWORD_TO_CONSTRAINT_CLASS_MAP[suffix]
else:
name = self.name
cls = self.DEFAULT_CONSTRAINT_CLASS
return cls(name, self.value)
def get_name_and_keyword(self, name):
if not self.KEYWORD_SEPARATOR in name:
return name, None
return name.split(self.KEYWORD_SEPARATOR)