10
10
from pynamodb .expressions .util import get_path_segments , get_value_placeholder , substitute_names
11
11
12
12
13
- class Operand (object ):
13
+ class _Operand (object ):
14
14
"""
15
15
Operand is the base class for objects that can be operands in Condition and Update Expressions.
16
16
"""
17
+ format_string = ''
18
+ short_attr_type = None
19
+
20
+ def __init__ (self , * values ):
21
+ self .values = values
22
+
23
+ def __repr__ (self ):
24
+ return self .format_string .format (* self .values )
25
+
26
+ def serialize (self , placeholder_names , expression_attribute_values ):
27
+ values = [self ._serialize_value (value , placeholder_names , expression_attribute_values ) for value in self .values ]
28
+ return self .format_string .format (* values )
29
+
30
+ def _serialize_value (self , value , placeholder_names , expression_attribute_values ):
31
+ return value .serialize (placeholder_names , expression_attribute_values )
32
+
33
+ def _to_operand (self , value ):
34
+ if isinstance (value , _Operand ):
35
+ return value
36
+ from pynamodb .attributes import Attribute # prevent circular import -- Attribute imports Path
37
+ return Path (value ) if isinstance (value , Attribute ) else self ._to_value (value )
38
+
39
+ def _to_value (self , value ):
40
+ return Value (value )
41
+
42
+ def _type_check (self , * types ):
43
+ if self .short_attr_type and self .short_attr_type not in types :
44
+ raise ValueError ("The data type of '{0}' must be one of {1}" .format (self , list (types )))
45
+
46
+
47
+ class _ConditionOperand (_Operand ):
48
+ """
49
+ A base class for Operands that can be used in Condition Expression comparisons.
50
+ """
17
51
18
52
def __eq__ (self , other ):
19
53
return Comparison ('=' , self , self ._to_operand (other ))
@@ -40,86 +74,94 @@ def is_in(self, *values):
40
74
values = [self ._to_operand (value ) for value in values ]
41
75
return In (self , * values )
42
76
43
- def serialize (self , placeholder_names , expression_attribute_values ):
44
- raise NotImplementedError ('serialize has not been implemented for {0}' .format (self .__class__ .__name__ ))
45
77
46
- def _has_type (self , short_type ):
47
- raise NotImplementedError ('_has_type has not been implemented for {0}' .format (self .__class__ .__name__ ))
78
+ class _Size (_ConditionOperand ):
79
+ """
80
+ Size is a special operand that represents the result of calling the 'size' function on a Path operand.
81
+ """
82
+ format_string = 'size ({0})'
83
+ short_attr_type = NUMBER_SHORT
84
+
85
+ def __init__ (self , path ):
86
+ if not isinstance (path , Path ):
87
+ path = Path (path )
88
+ super (_Size , self ).__init__ (path )
48
89
49
90
def _to_operand (self , value ):
50
- from pynamodb .attributes import Attribute # prevent circular import -- Attribute imports AttributePath
51
- if isinstance (value , Attribute ):
52
- return AttributePath (value )
53
- return value if isinstance (value , Operand ) else self ._to_value (value )
54
-
55
- def _to_value (self , value ):
56
- return Value (value )
91
+ operand = super (_Size , self )._to_operand (value )
92
+ operand ._type_check (NUMBER_SHORT )
93
+ return operand
57
94
58
95
59
- class Value (Operand ):
96
+ class Value (_ConditionOperand ):
60
97
"""
61
98
Value is an operand that represents an attribute value.
62
99
"""
100
+ format_string = '{0}'
63
101
64
102
def __init__ (self , value , attribute = None ):
65
103
# Check to see if value is already serialized
66
104
if isinstance (value , dict ) and len (value ) == 1 and list (value .keys ())[0 ] in SHORT_ATTR_TYPES :
67
- self .value = value
105
+ ( self .short_attr_type , value ), = value . items ()
68
106
else :
69
- self .value = Value ._serialize_value (value , attribute )
107
+ (self .short_attr_type , value ) = Value .__serialize (value , attribute )
108
+ super (Value , self ).__init__ ({self .short_attr_type : value })
70
109
71
- def serialize (self , placeholder_names , expression_attribute_values ):
72
- return get_value_placeholder (self .value , expression_attribute_values )
110
+ @property
111
+ def value (self ):
112
+ return self .values [0 ]
73
113
74
- def _has_type (self , short_type ):
75
- (attr_type , value ), = self .value .items ()
76
- return short_type == attr_type
77
-
78
- def __str__ (self ):
79
- (attr_type , value ), = self .value .items ()
80
- try :
81
- from pynamodb .attributes import _get_class_for_deserialize
82
- attr_class = _get_class_for_deserialize (self .value )
83
- return str (attr_class .deserialize (value ))
84
- except ValueError :
85
- return str (value )
86
-
87
- def __repr__ (self ):
88
- return "Value({0})" .format (self .value )
114
+ def _serialize_value (self , value , placeholder_names , expression_attribute_values ):
115
+ return get_value_placeholder (value , expression_attribute_values )
89
116
90
117
@staticmethod
91
- def _serialize_value (value , attribute = None ):
118
+ def __serialize (value , attribute = None ):
92
119
if attribute is None :
93
- return Value ._serialize_value_based_on_type (value )
120
+ return Value .__serialize_based_on_type (value )
94
121
if attribute .attr_type == LIST and not isinstance (value , list ):
95
122
# List attributes assume the values to be serialized are lists.
96
- return attribute .serialize ([value ])[0 ]
123
+ (attr_type , attr_value ), = attribute .serialize ([value ])[0 ].items ()
124
+ return attr_type , attr_value
97
125
if attribute .attr_type == MAP and not isinstance (value , dict ):
98
126
# Map attributes assume the values to be serialized are maps.
99
- return Value ._serialize_value_based_on_type (value )
100
- return { ATTR_TYPE_MAP [attribute .attr_type ]: attribute .serialize (value )}
127
+ return Value .__serialize_based_on_type (value )
128
+ return ATTR_TYPE_MAP [attribute .attr_type ], attribute .serialize (value )
101
129
102
130
@staticmethod
103
- def _serialize_value_based_on_type (value ):
131
+ def __serialize_based_on_type (value ):
104
132
from pynamodb .attributes import _get_class_for_serialize
105
133
attr_class = _get_class_for_serialize (value )
106
- return { ATTR_TYPE_MAP [attr_class .attr_type ]: attr_class .serialize (value )}
134
+ return ATTR_TYPE_MAP [attr_class .attr_type ], attr_class .serialize (value )
107
135
108
136
109
- class Path (Operand ):
137
+ class Path (_ConditionOperand ):
110
138
"""
111
139
Path is an operand that represents either an attribute name or document path.
112
140
"""
113
-
114
- def __init__ (self , path ):
141
+ format_string = '{0}'
142
+
143
+ def __init__ (self , attribute_or_path ):
144
+ from pynamodb .attributes import Attribute # prevent circular import -- Attribute imports Path
145
+ is_attribute = isinstance (attribute_or_path , Attribute )
146
+ self .attribute = attribute_or_path if is_attribute else None
147
+ self .short_attr_type = ATTR_TYPE_MAP [attribute_or_path .attr_type ] if is_attribute else None
148
+ path = attribute_or_path .attr_path if is_attribute else attribute_or_path
115
149
if not path :
116
150
raise ValueError ("path cannot be empty" )
117
- self .path = get_path_segments (path )
151
+ super (Path , self ).__init__ (get_path_segments (path ))
152
+
153
+ @property
154
+ def path (self ):
155
+ return self .values [0 ]
118
156
119
157
def __getitem__ (self , idx ):
120
158
# list dereference operator
159
+ if self .attribute and self .attribute .attr_type != LIST :
160
+ raise TypeError ("'{0}' object has no attribute __getitem__" .format (self .attribute .__class__ .__name__ ))
121
161
if not isinstance (idx , int ):
122
162
raise TypeError ("list indices must be integers, not {0}" .format (type (idx ).__name__ ))
163
+ # The __getitem__ call returns a new Path instance without any attribute set.
164
+ # This is intended since the list element is not the same attribute as the list itself.
123
165
element_path = Path (self .path ) # copy the document path before indexing last element
124
166
element_path .path [- 1 ] = '{0}[{1}]' .format (self .path [- 1 ], idx )
125
167
return element_path
@@ -155,19 +197,21 @@ def is_type(self, attr_type):
155
197
def startswith (self , prefix ):
156
198
# A 'pythonic' replacement for begins_with to match string behavior (e.g. "foo".startswith("f"))
157
199
operand = self ._to_operand (prefix )
158
- if not operand ._has_type (STRING_SHORT ):
159
- raise ValueError ("{0} must be a string operand" .format (operand ))
200
+ operand ._type_check (STRING_SHORT )
160
201
return BeginsWith (self , operand )
161
202
162
203
def contains (self , item ):
204
+ if self .attribute and self .attribute .attr_type in [BINARY_SET , NUMBER_SET , STRING_SET ]:
205
+ # Set attributes assume the values to be serialized are sets.
206
+ (attr_type , attr_value ), = self ._to_value ([item ]).value .items ()
207
+ item = {attr_type [0 ]: attr_value [0 ]}
163
208
return Contains (self , self ._to_operand (item ))
164
209
165
- def serialize (self , placeholder_names , expression_attribute_values ):
166
- return substitute_names (self . path , placeholder_names )
210
+ def _serialize_value (self , value , placeholder_names , expression_attribute_values ):
211
+ return substitute_names (value , placeholder_names )
167
212
168
- def _has_type (self , short_type ):
169
- # Assume the attribute has the correct type
170
- return True
213
+ def _to_value (self , value ):
214
+ return Value (value , attribute = self .attribute )
171
215
172
216
def __str__ (self ):
173
217
# Quote the path to illustrate that any dot characters are not dereference operators.
@@ -181,64 +225,3 @@ def __repr__(self):
181
225
def _quote_path (path ):
182
226
path , sep , rem = path .partition ('[' )
183
227
return repr (path ) + sep + rem
184
-
185
-
186
- class AttributePath (Path ):
187
-
188
- def __init__ (self , attribute ):
189
- super (AttributePath , self ).__init__ (attribute .attr_path )
190
- self .attribute = attribute
191
-
192
- def __getitem__ (self , idx ):
193
- if self .attribute .attr_type != LIST : # only list elements support the list dereference operator
194
- raise TypeError ("'{0}' object has no attribute __getitem__" .format (self .attribute .__class__ .__name__ ))
195
- # The __getitem__ call returns a new Path instance, not an AttributePath instance.
196
- # This is intended since the list element is not the same attribute as the list itself.
197
- return super (AttributePath , self ).__getitem__ (idx )
198
-
199
- def contains (self , item ):
200
- if self .attribute .attr_type in [BINARY_SET , NUMBER_SET , STRING_SET ]:
201
- # Set attributes assume the values to be serialized are sets.
202
- (attr_type , attr_value ), = self ._to_value ([item ]).value .items ()
203
- item = {attr_type [0 ]: attr_value [0 ]}
204
- return super (AttributePath , self ).contains (item )
205
-
206
- def _has_type (self , short_type ):
207
- return ATTR_TYPE_MAP [self .attribute .attr_type ] == short_type
208
-
209
- def _to_value (self , value ):
210
- return Value (value , attribute = self .attribute )
211
-
212
-
213
- class Size (Operand ):
214
- """
215
- Size is a special operand that represents the result of calling the 'size' function on a Path operand.
216
- """
217
-
218
- def __init__ (self , path ):
219
- # prevent circular import -- Attribute imports AttributePath
220
- from pynamodb .attributes import Attribute
221
- if isinstance (path , Path ):
222
- self .path = path
223
- elif isinstance (path , Attribute ):
224
- self .path = AttributePath (path )
225
- else :
226
- self .path = Path (path )
227
-
228
- def _to_operand (self , value ):
229
- operand = super (Size , self )._to_operand (value )
230
- if not operand ._has_type (NUMBER_SHORT ):
231
- raise ValueError ("size must be compared to a number, not {0}" .format (operand ))
232
- return operand
233
-
234
- def serialize (self , placeholder_names , expression_attribute_values ):
235
- return "size ({0})" .format (substitute_names (self .path .path , placeholder_names ))
236
-
237
- def _has_type (self , short_type ):
238
- return short_type == NUMBER_SHORT
239
-
240
- def __str__ (self ):
241
- return "size({0})" .format (self .path )
242
-
243
- def __repr__ (self ):
244
- return "Size({0})" .format (repr (self .path ))
0 commit comments