Skip to content

Commit e5d5b3e

Browse files
authored
Expression operands can be attribute names or attribute values. (#349)
1 parent ac995b9 commit e5d5b3e

File tree

4 files changed

+178
-239
lines changed

4 files changed

+178
-239
lines changed

pynamodb/expressions/condition.py

Lines changed: 36 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
from pynamodb.constants import (
2-
AND, BETWEEN, BINARY_SHORT, IN, NUMBER_SHORT, OR, SHORT_ATTR_TYPES, STRING_SHORT
3-
)
4-
from pynamodb.expressions.util import get_value_placeholder, substitute_names
1+
from pynamodb.constants import AND, BETWEEN, IN, OR
52
from six.moves import range
63

74

@@ -12,38 +9,19 @@ def size(path):
129

1310

1411
class Condition(object):
15-
format_string = '{path} {operator} {0}'
12+
format_string = ''
1613

17-
def __init__(self, path, operator, *values):
18-
self.path = path
14+
def __init__(self, operator, *values):
1915
self.operator = operator
2016
self.values = values
2117

2218
# http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-KeyConditionExpression
2319
def is_valid_range_key_condition(self, path):
24-
return str(self.path) == path and self.operator in ['=', '<', '<=', '>', '>=', BETWEEN, 'begins_with']
20+
return self.operator in ['=', '<', '<=', '>', '>=', BETWEEN, 'begins_with'] and str(self.values[0]) == path
2521

2622
def serialize(self, placeholder_names, expression_attribute_values):
27-
path = self._get_path(self.path, placeholder_names)
28-
values = self._get_values(placeholder_names, expression_attribute_values)
29-
return self.format_string.format(*values, path=path, operator=self.operator)
30-
31-
def _get_path(self, path, placeholder_names):
32-
from pynamodb.expressions.operand import Path, Size
33-
if isinstance(path, Path):
34-
return substitute_names(path.path, placeholder_names)
35-
elif isinstance(path, Size):
36-
return "size ({0})".format(self._get_path(path.path, placeholder_names))
37-
else:
38-
return path
39-
40-
def _get_values(self, placeholder_names, expression_attribute_values):
41-
return [
42-
value.serialize(placeholder_names, expression_attribute_values)
43-
if isinstance(value, Condition)
44-
else get_value_placeholder(value, expression_attribute_values)
45-
for value in self.values
46-
]
23+
values = [value.serialize(placeholder_names, expression_attribute_values) for value in self.values]
24+
return self.format_string.format(*values, operator=self.operator)
4725

4826
def __and__(self, other):
4927
if not isinstance(other, Condition):
@@ -61,8 +39,8 @@ def __invert__(self):
6139
return Not(self)
6240

6341
def __repr__(self):
64-
values = [repr(value) if isinstance(value, Condition) else list(value.items())[0][1] for value in self.values]
65-
return self.format_string.format(*values, path=self.path, operator = self.operator)
42+
values = [str(value) for value in self.values]
43+
return self.format_string.format(*values, operator=self.operator)
6644

6745
def __nonzero__(self):
6846
# Prevent users from accidentally comparing the condition object instead of the attribute instance
@@ -73,77 +51,80 @@ def __bool__(self):
7351
raise TypeError("unsupported operand type(s) for bool: {0}".format(self.__class__.__name__))
7452

7553

54+
class Comparison(Condition):
55+
format_string = '{0} {operator} {1}'
56+
57+
def __init__(self, operator, lhs, rhs):
58+
if operator not in ['=', '<>', '<', '<=', '>', '>=']:
59+
raise ValueError("{0} is not a valid comparison operator: {0}".format(operator))
60+
super(Comparison, self).__init__(operator, lhs, rhs)
61+
62+
7663
class Between(Condition):
77-
format_string = '{path} {operator} {0} AND {1}'
64+
format_string = '{0} {operator} {1} AND {2}'
7865

7966
def __init__(self, path, lower, upper):
80-
super(Between, self).__init__(path, BETWEEN, lower, upper)
67+
super(Between, self).__init__(BETWEEN, path, lower, upper)
8168

8269

8370
class In(Condition):
8471
def __init__(self, path, *values):
85-
super(In, self).__init__(path, IN, *values)
86-
list_format = ', '.join('{' + str(i) + '}' for i in range(len(values)))
87-
self.format_string = '{path} {operator} (' + list_format + ')'
72+
super(In, self).__init__(IN, path, *values)
73+
list_format = ', '.join('{' + str(i + 1) + '}' for i in range(len(values)))
74+
self.format_string = '{0} {operator} (' + list_format + ')'
8875

8976

9077
class Exists(Condition):
91-
format_string = '{operator} ({path})'
78+
format_string = '{operator} ({0})'
9279

9380
def __init__(self, path):
94-
super(Exists, self).__init__(path, 'attribute_exists')
81+
super(Exists, self).__init__('attribute_exists', path)
9582

9683

9784
class NotExists(Condition):
98-
format_string = '{operator} ({path})'
85+
format_string = '{operator} ({0})'
9986

10087
def __init__(self, path):
101-
super(NotExists, self).__init__(path, 'attribute_not_exists')
88+
super(NotExists, self).__init__('attribute_not_exists', path)
10289

10390

10491
class IsType(Condition):
105-
format_string = '{operator} ({path}, {0})'
92+
format_string = '{operator} ({0}, {1})'
10693

10794
def __init__(self, path, attr_type):
108-
if attr_type not in SHORT_ATTR_TYPES:
109-
raise ValueError("{0} is not a valid attribute type. Must be one of {1}".format(
110-
attr_type, SHORT_ATTR_TYPES))
111-
super(IsType, self).__init__(path, 'attribute_type', {STRING_SHORT: attr_type})
95+
super(IsType, self).__init__('attribute_type', path, attr_type)
11296

11397

11498
class BeginsWith(Condition):
115-
format_string = '{operator} ({path}, {0})'
99+
format_string = '{operator} ({0}, {1})'
116100

117101
def __init__(self, path, prefix):
118-
super(BeginsWith, self).__init__(path, 'begins_with', prefix)
102+
super(BeginsWith, self).__init__('begins_with', path, prefix)
119103

120104

121105
class Contains(Condition):
122-
format_string = '{operator} ({path}, {0})'
106+
format_string = '{operator} ({0}, {1})'
123107

124-
def __init__(self, path, item):
125-
(attr_type, value), = item.items()
126-
if attr_type not in [BINARY_SHORT, NUMBER_SHORT, STRING_SHORT]:
127-
raise ValueError("{0} must be a string, number, or binary element".format(value))
128-
super(Contains, self).__init__(path, 'contains', item)
108+
def __init__(self, path, operand):
109+
super(Contains, self).__init__('contains', path, operand)
129110

130111

131112
class And(Condition):
132113
format_string = '({0} {operator} {1})'
133114

134115
def __init__(self, condition1, condition2):
135-
super(And, self).__init__(None, AND, condition1, condition2)
116+
super(And, self).__init__(AND, condition1, condition2)
136117

137118

138119
class Or(Condition):
139120
format_string = '({0} {operator} {1})'
140121

141122
def __init__(self, condition1, condition2):
142-
super(Or, self).__init__(None, OR, condition1, condition2)
123+
super(Or, self).__init__(OR, condition1, condition2)
143124

144125

145126
class Not(Condition):
146127
format_string = '({operator} {0})'
147128

148129
def __init__(self, condition):
149-
super(Not, self).__init__(None, 'NOT', condition)
130+
super(Not, self).__init__('NOT', condition)

0 commit comments

Comments
 (0)