78 |
81 |
79 |
82 |
80 def ValidateString(value, |
83 def ValidateString(value, |
81 name='unused', |
84 name='unused', |
82 exception=datastore_errors.BadValueError, |
85 exception=datastore_errors.BadValueError, |
83 max_len=_MAX_STRING_LENGTH): |
86 max_len=_MAX_STRING_LENGTH, |
|
87 empty_ok=False): |
84 """Raises an exception if value is not a valid string or a subclass thereof. |
88 """Raises an exception if value is not a valid string or a subclass thereof. |
85 |
89 |
86 A string is valid if it's not empty, no more than _MAX_STRING_LENGTH bytes, |
90 A string is valid if it's not empty, no more than _MAX_STRING_LENGTH bytes, |
87 and not a Blob. The exception type can be specified with the exception |
91 and not a Blob. The exception type can be specified with the exception |
88 argument; it defaults to BadValueError. |
92 argument; it defaults to BadValueError. |
89 |
93 |
90 Args: |
94 Args: |
91 value: the value to validate. |
95 value: the value to validate. |
92 name: the name of this value; used in the exception message. |
96 name: the name of this value; used in the exception message. |
93 exception: the type of exception to raise. |
97 exception: the type of exception to raise. |
94 max_len: the maximum allowed length, in bytes |
98 max_len: the maximum allowed length, in bytes. |
95 """ |
99 empty_ok: allow empty value. |
|
100 """ |
|
101 if value is None and empty_ok: |
|
102 return |
96 if not isinstance(value, basestring) or isinstance(value, Blob): |
103 if not isinstance(value, basestring) or isinstance(value, Blob): |
97 raise exception('%s should be a string; received %s (a %s):' % |
104 raise exception('%s should be a string; received %s (a %s):' % |
98 (name, value, typename(value))) |
105 (name, value, typename(value))) |
99 if not value: |
106 if not value and not empty_ok: |
100 raise exception('%s must not be empty.' % name) |
107 raise exception('%s must not be empty.' % name) |
101 |
108 |
102 if len(value.encode('utf-8')) > max_len: |
109 if len(value.encode('utf-8')) > max_len: |
103 raise exception('%s must be under %d bytes.' % (name, max_len)) |
110 raise exception('%s must be under %d bytes.' % (name, max_len)) |
104 |
111 |
|
112 def ValidateInteger(value, |
|
113 name='unused', |
|
114 exception=datastore_errors.BadValueError, |
|
115 empty_ok=False, |
|
116 zero_ok=False, |
|
117 negative_ok=False): |
|
118 """Raises an exception if value is not a valid integer. |
|
119 |
|
120 An integer is valid if it's not negative or empty and is an integer. |
|
121 The exception type can be specified with the exception argument; |
|
122 it defaults to BadValueError. |
|
123 |
|
124 Args: |
|
125 value: the value to validate. |
|
126 name: the name of this value; used in the exception message. |
|
127 exception: the type of exception to raise. |
|
128 empty_ok: allow None value. |
|
129 zero_ok: allow zero value. |
|
130 negative_ok: allow negative value. |
|
131 """ |
|
132 if value is None and empty_ok: |
|
133 return |
|
134 if not isinstance(value, int): |
|
135 raise exception('%s should be an integer; received %s (a %s).' % |
|
136 (name, value, typename(value))) |
|
137 if not value and not zero_ok: |
|
138 raise exception('%s must not be 0 (zero)' % name) |
|
139 if value < 0 and not negative_ok: |
|
140 raise exception('%s must not be negative.' % name) |
105 |
141 |
106 def ResolveAppId(app, name='_app'): |
142 def ResolveAppId(app, name='_app'): |
107 """Validate app id, providing a default. |
143 """Validate app id, providing a default. |
108 |
144 |
109 If the argument is None, $APPLICATION_ID is substituted. |
145 If the argument is None, $APPLICATION_ID is substituted. |
120 """ |
156 """ |
121 if app is None: |
157 if app is None: |
122 app = os.environ.get('APPLICATION_ID', '') |
158 app = os.environ.get('APPLICATION_ID', '') |
123 ValidateString(app, '_app', datastore_errors.BadArgumentError) |
159 ValidateString(app, '_app', datastore_errors.BadArgumentError) |
124 return app |
160 return app |
|
161 |
|
162 |
|
163 class AppIdNamespace(object): |
|
164 """Combined AppId and Namespace |
|
165 |
|
166 An identifier that combines the application identifier and the |
|
167 namespace. |
|
168 """ |
|
169 __app_id = None |
|
170 __namespace = None |
|
171 |
|
172 def __init__(self, app_id, namespace): |
|
173 """Constructor. Creates a AppIdNamespace from two strings. |
|
174 |
|
175 Args: |
|
176 app_id: application identifier string |
|
177 namespace: namespace identifier string |
|
178 Raises: |
|
179 BadArgumentError if the values contain |
|
180 the _NAMESPACE_SEPARATOR character (!) or |
|
181 the app_id is empty. |
|
182 """ |
|
183 self.__app_id = app_id |
|
184 if namespace: |
|
185 self.__namespace = namespace |
|
186 else: |
|
187 self.__namespace = None |
|
188 ValidateString(self.__app_id, 'app_id', datastore_errors.BadArgumentError) |
|
189 ValidateString(self.__namespace, |
|
190 'namespace', datastore_errors.BadArgumentError, |
|
191 empty_ok=True) |
|
192 if _NAMESPACE_SEPARATOR in self.__app_id: |
|
193 raise datastore_errors.BadArgumentError( |
|
194 'app_id must not contain a "%s"' % _NAMESPACE_SEPARATOR) |
|
195 if self.__namespace and _NAMESPACE_SEPARATOR in self.__namespace: |
|
196 raise datastore_errors.BadArgumentError( |
|
197 'namespace must not contain a "%s"' % _NAMESPACE_SEPARATOR) |
|
198 |
|
199 def __cmp__(self, other): |
|
200 """Returns negative, zero, or positive when comparing two AppIdNamespace. |
|
201 |
|
202 Args: |
|
203 other: AppIdNamespace to compare to. |
|
204 |
|
205 Returns: |
|
206 Negative if self is less than "other" |
|
207 Zero if "other" is equal to self |
|
208 Positive if self is greater than "other" |
|
209 """ |
|
210 if not isinstance(other, AppIdNamespace): |
|
211 return cmp(id(self), id(other)) |
|
212 return cmp((self.__app_id, self.__namespace), |
|
213 (other.__app_id, other.__namespace)) |
|
214 |
|
215 def to_encoded(self): |
|
216 """Returns this AppIdNamespace's string equivalent |
|
217 |
|
218 i.e. "app!namespace" |
|
219 """ |
|
220 if not self.__namespace: |
|
221 return self.__app_id |
|
222 else: |
|
223 return self.__app_id + _NAMESPACE_SEPARATOR + self.__namespace |
|
224 |
|
225 def app_id(self): |
|
226 """Returns this AppId portion of this AppIdNamespace. |
|
227 """ |
|
228 return self.__app_id; |
|
229 |
|
230 def namespace(self): |
|
231 """Returns this namespace portion of this AppIdNamespace. |
|
232 """ |
|
233 return self.__namespace; |
|
234 |
|
235 |
|
236 def PartitionString(value, separator): |
|
237 """Equivalent to python2.5 str.partition() |
|
238 TODO(gmariani) use str.partition() when python 2.5 is adopted. |
|
239 |
|
240 Args: |
|
241 value: String to be partitioned |
|
242 separator: Separator string |
|
243 """ |
|
244 index = value.find(separator); |
|
245 if index == -1: |
|
246 return (value, '', value[0:0]); |
|
247 else: |
|
248 return (value[0:index], separator, value[index+len(separator):len(value)]) |
|
249 |
|
250 |
|
251 def parse_app_id_namespace(app_id_namespace): |
|
252 """ |
|
253 An app_id_namespace string is valid if it's not empty, and contains |
|
254 at most one namespace separator ('!'). Also, an app_id_namespace |
|
255 with an empty namespace must not contain a namespace separator. |
|
256 |
|
257 Args: |
|
258 app_id_namespace: an encoded app_id_namespace. |
|
259 Raises exception if format of app_id_namespace is invalid. |
|
260 """ |
|
261 if not app_id_namespace: |
|
262 raise datastore_errors.BadArgumentError( |
|
263 'app_id_namespace must be non empty') |
|
264 parts = PartitionString(app_id_namespace, _NAMESPACE_SEPARATOR) |
|
265 if parts[1] == _NAMESPACE_SEPARATOR: |
|
266 if not parts[2]: |
|
267 raise datastore_errors.BadArgumentError( |
|
268 'app_id_namespace must not contain a "%s" if the namespace is empty' % |
|
269 _NAMESPACE_SEPARATOR) |
|
270 if parts[2]: |
|
271 return AppIdNamespace(parts[0], parts[2]) |
|
272 return AppIdNamespace(parts[0], None) |
|
273 |
|
274 def ResolveAppIdNamespace( |
|
275 app_id=None, namespace=None, app_id_namespace=None): |
|
276 """Validate an app id/namespace and substitute default values. |
|
277 |
|
278 If the argument is None, $APPLICATION_ID!$NAMESPACE is substituted. |
|
279 |
|
280 Args: |
|
281 app_id: The app id argument value to be validated. |
|
282 namespace: The namespace argument value to be validated. |
|
283 app_id_namespace: An AppId/Namespace pair |
|
284 |
|
285 Returns: |
|
286 An AppIdNamespace object initialized with AppId and Namespace. |
|
287 |
|
288 Raises: |
|
289 BadArgumentError if the value is empty or not a string. |
|
290 """ |
|
291 if app_id_namespace is None: |
|
292 if app_id is None: |
|
293 app_id = os.environ.get('APPLICATION_ID', '') |
|
294 if namespace is None: |
|
295 namespace = namespace_manager.get_request_namespace(); |
|
296 else: |
|
297 if not app_id is None: |
|
298 raise datastore_errors.BadArgumentError( |
|
299 'app_id is overspecified. Cannot define app_id_namespace and app_id') |
|
300 if not namespace is None: |
|
301 raise datastore_errors.BadArgumentError( |
|
302 'namespace is overspecified. ' + |
|
303 'Cannot define app_id_namespace and namespace') |
|
304 return parse_app_id_namespace(app_id_namespace) |
|
305 |
|
306 return AppIdNamespace(app_id, namespace) |
125 |
307 |
126 |
308 |
127 class Key(object): |
309 class Key(object): |
128 """The primary key for a datastore entity. |
310 """The primary key for a datastore entity. |
129 |
311 |
170 else: |
352 else: |
171 raise |
353 raise |
172 else: |
354 else: |
173 self.__reference = entity_pb.Reference() |
355 self.__reference = entity_pb.Reference() |
174 |
356 |
|
357 def to_path(self): |
|
358 """Construct the "path" of this key as a list. |
|
359 |
|
360 Returns: |
|
361 A list [kind_1, id_or_name_1, ..., kind_n, id_or_name_n] of the key path. |
|
362 |
|
363 Raises: |
|
364 datastore_errors.BadKeyError if this key does not have a valid path. |
|
365 """ |
|
366 path = [] |
|
367 for path_element in self.__reference.path().element_list(): |
|
368 path.append(path_element.type().decode('utf-8')) |
|
369 if path_element.has_name(): |
|
370 path.append(path_element.name().decode('utf-8')) |
|
371 elif path_element.has_id(): |
|
372 path.append(path_element.id()) |
|
373 else: |
|
374 raise datastore_errors.BadKeyError('Incomplete key found in to_path') |
|
375 return path |
|
376 |
175 @staticmethod |
377 @staticmethod |
176 def from_path(*args, **kwds): |
378 def from_path(*args, **kwds): |
177 """Static method to construct a Key out of a "path" (kind, id or name, ...). |
379 """Static method to construct a Key out of a "path" (kind, id or name, ...). |
178 |
380 |
179 This is useful when an application wants to use just the id or name portion |
381 This is useful when an application wants to use just the id or name portion |
219 'Expected None or a Key as parent; received %r (a %s).' % |
424 'Expected None or a Key as parent; received %r (a %s).' % |
220 (parent, typename(parent))) |
425 (parent, typename(parent))) |
221 if not parent.has_id_or_name(): |
426 if not parent.has_id_or_name(): |
222 raise datastore_errors.BadKeyError( |
427 raise datastore_errors.BadKeyError( |
223 'The parent Key is incomplete.') |
428 'The parent Key is incomplete.') |
224 if _app != parent.app(): |
429 if _app_id_namespace_obj != parent.app_id_namespace(): |
225 raise datastore_errors.BadArgumentError( |
430 raise datastore_errors.BadArgumentError( |
226 'The _app argument (%r) should match parent.app() (%s)' % |
431 'The app_id/namespace arguments (%r) should match ' + |
227 (_app, parent.app())) |
432 'parent.app_id_namespace().to_encoded() (%s)' % |
|
433 (_app_id_namespace_obj, parent.app_id_namespace())) |
228 |
434 |
229 key = Key() |
435 key = Key() |
230 ref = key.__reference |
436 ref = key.__reference |
231 if parent is not None: |
437 if parent is not None: |
232 ref.CopyFrom(parent.__reference) |
438 ref.CopyFrom(parent.__reference) |
233 else: |
439 else: |
234 ref.set_app(_app) |
440 ref.set_app(_app_id_namespace_obj.to_encoded()) |
235 |
441 |
236 path = ref.mutable_path() |
442 path = ref.mutable_path() |
237 for i in xrange(0, len(args), 2): |
443 for i in xrange(0, len(args), 2): |
238 kind, id_or_name = args[i:i+2] |
444 kind, id_or_name = args[i:i+2] |
239 if isinstance(kind, basestring): |
445 if isinstance(kind, basestring): |
246 elem.set_type(kind) |
452 elem.set_type(kind) |
247 if isinstance(id_or_name, (int, long)): |
453 if isinstance(id_or_name, (int, long)): |
248 elem.set_id(id_or_name) |
454 elem.set_id(id_or_name) |
249 elif isinstance(id_or_name, basestring): |
455 elif isinstance(id_or_name, basestring): |
250 ValidateString(id_or_name, 'name') |
456 ValidateString(id_or_name, 'name') |
251 if id_or_name and id_or_name[0] in string.digits: |
|
252 raise datastore_errors.BadArgumentError( |
|
253 'Names may not begin with a digit; received %s.' % id_or_name) |
|
254 elem.set_name(id_or_name.encode('utf-8')) |
457 elem.set_name(id_or_name.encode('utf-8')) |
255 else: |
458 else: |
256 raise datastore_errors.BadArgumentError( |
459 raise datastore_errors.BadArgumentError( |
257 'Expected an integer id or string name as argument %d; ' |
460 'Expected an integer id or string name as argument %d; ' |
258 'received %r (a %s).' % (i + 2, id_or_name, typename(id_or_name))) |
461 'received %r (a %s).' % (i + 2, id_or_name, typename(id_or_name))) |
337 """ |
554 """ |
338 if not self.has_id_or_name(): |
555 if not self.has_id_or_name(): |
339 raise datastore_errors.BadKeyError( |
556 raise datastore_errors.BadKeyError( |
340 'ToTagUri() called for an entity with an incomplete key.') |
557 'ToTagUri() called for an entity with an incomplete key.') |
341 |
558 |
342 return u'tag:%s.%s,%s:%s[%s]' % (saxutils.escape(self.app()), |
559 return u'tag:%s.%s,%s:%s[%s]' % ( |
343 os.environ['AUTH_DOMAIN'], |
560 saxutils.escape(self.app_id_namespace().to_encoded()), |
344 datetime.date.today().isoformat(), |
561 os.environ['AUTH_DOMAIN'], |
345 saxutils.escape(self.kind()), |
562 datetime.date.today().isoformat(), |
346 saxutils.escape(str(self))) |
563 saxutils.escape(self.kind()), |
|
564 saxutils.escape(str(self))) |
|
565 |
347 ToXml = ToTagUri |
566 ToXml = ToTagUri |
348 |
567 |
349 def entity_group(self): |
568 def entity_group(self): |
350 """Returns this key's entity group as a Key. |
569 """Returns this key's entity group as a Key. |
351 |
570 |
457 return -2 |
676 return -2 |
458 |
677 |
459 self_args = [] |
678 self_args = [] |
460 other_args = [] |
679 other_args = [] |
461 |
680 |
462 self_args.append(self.__reference.app().decode('utf-8')) |
681 self_args.append(self.__reference.app()) |
463 other_args.append(other.__reference.app().decode('utf-8')) |
682 other_args.append(other.__reference.app()) |
464 |
683 |
465 for elem in self.__reference.path().element_list(): |
684 for elem in self.__reference.path().element_list(): |
466 self_args.append(repr(elem.type())) |
685 self_args.append(elem.type()) |
467 if elem.has_name(): |
686 if elem.has_name(): |
468 self_args.append(repr(elem.name().decode('utf-8'))) |
687 self_args.append(elem.name()) |
469 else: |
688 else: |
470 self_args.append(elem.id()) |
689 self_args.append(elem.id()) |
471 |
690 |
472 for elem in other.__reference.path().element_list(): |
691 for elem in other.__reference.path().element_list(): |
473 other_args.append(repr(elem.type())) |
692 other_args.append(elem.type()) |
474 if elem.has_name(): |
693 if elem.has_name(): |
475 other_args.append(repr(elem.name().decode('utf-8'))) |
694 other_args.append(elem.name()) |
476 else: |
695 else: |
477 other_args.append(elem.id()) |
696 other_args.append(elem.id()) |
478 |
697 |
479 result = cmp(self_args, other_args) |
698 for self_component, other_component in zip(self_args, other_args): |
480 return result |
699 comparison = cmp(self_component, other_component) |
|
700 if comparison != 0: |
|
701 return comparison |
|
702 |
|
703 return cmp(len(self_args), len(other_args)) |
481 |
704 |
482 def __hash__(self): |
705 def __hash__(self): |
483 """Returns a 32-bit integer hash of this key. |
706 """Returns a 32-bit integer hash of this key. |
484 |
707 |
485 Implements Python's hash protocol so that Keys may be used in sets and as |
708 Implements Python's hash protocol so that Keys may be used in sets and as |
896 Returns: |
1120 Returns: |
897 Base64 encoded version of itself for safe insertion in to an XML document. |
1121 Base64 encoded version of itself for safe insertion in to an XML document. |
898 """ |
1122 """ |
899 encoded = base64.urlsafe_b64encode(self) |
1123 encoded = base64.urlsafe_b64encode(self) |
900 return saxutils.escape(encoded) |
1124 return saxutils.escape(encoded) |
|
1125 |
|
1126 |
|
1127 class BlobKey(object): |
|
1128 """Key used to identify a blob in Blobstore. |
|
1129 |
|
1130 This object wraps a string that gets used internally by the Blobstore API |
|
1131 to identify application blobs. The BlobKey corresponds to the entity name |
|
1132 of the underlying BlobReference entity. The structure of the key is: |
|
1133 |
|
1134 _<blob-key> |
|
1135 |
|
1136 This class is exposed in the API in both google.appengine.ext.db and |
|
1137 google.appengine.ext.blobstore. |
|
1138 """ |
|
1139 |
|
1140 def __init__(self, blob_key): |
|
1141 """Constructor. |
|
1142 |
|
1143 Used to convert a string to a BlobKey. Normally used internally by |
|
1144 Blobstore API. |
|
1145 |
|
1146 Args: |
|
1147 blob_key: Key name of BlobReference that this key belongs to. |
|
1148 """ |
|
1149 self.__blob_key = blob_key |
|
1150 |
|
1151 def __str__(self): |
|
1152 """Convert to string.""" |
|
1153 return self.__blob_key |
|
1154 |
|
1155 def __repr__(self): |
|
1156 """Returns an eval()able string representation of this key. |
|
1157 |
|
1158 Returns a Python string of the form 'datastore_types.BlobKey(...)' |
|
1159 that can be used to recreate this key. |
|
1160 |
|
1161 Returns: |
|
1162 string |
|
1163 """ |
|
1164 s = type(self).__module__ |
|
1165 return '%s.%s(%r)' % (type(self).__module__, |
|
1166 type(self).__name__, |
|
1167 self.__blob_key) |
|
1168 |
|
1169 def __cmp__(self, other): |
|
1170 if type(other) is type(self): |
|
1171 return cmp(str(self), str(other)) |
|
1172 elif isinstance(other, basestring): |
|
1173 return cmp(self.__blob_key, other) |
|
1174 else: |
|
1175 return NotImplemented |
|
1176 |
|
1177 def __hash__(self): |
|
1178 return hash(self.__blob_key) |
|
1179 |
|
1180 def ToXml(self): |
|
1181 return str(self) |
901 |
1182 |
902 |
1183 |
903 _PROPERTY_MEANINGS = { |
1184 _PROPERTY_MEANINGS = { |
904 |
1185 |
905 |
1186 |
1329 |
1615 |
1330 lambda val: _EPOCH + datetime.timedelta(microseconds=val), |
1616 lambda val: _EPOCH + datetime.timedelta(microseconds=val), |
1331 entity_pb.Property.ATOM_CATEGORY: Category, |
1617 entity_pb.Property.ATOM_CATEGORY: Category, |
1332 entity_pb.Property.ATOM_LINK: Link, |
1618 entity_pb.Property.ATOM_LINK: Link, |
1333 entity_pb.Property.GD_EMAIL: Email, |
1619 entity_pb.Property.GD_EMAIL: Email, |
1334 entity_pb.Property.GEORSS_POINT: lambda coords: GeoPt(*coords), |
|
1335 entity_pb.Property.GD_IM: IM, |
1620 entity_pb.Property.GD_IM: IM, |
1336 entity_pb.Property.GD_PHONENUMBER: PhoneNumber, |
1621 entity_pb.Property.GD_PHONENUMBER: PhoneNumber, |
1337 entity_pb.Property.GD_POSTALADDRESS: PostalAddress, |
1622 entity_pb.Property.GD_POSTALADDRESS: PostalAddress, |
1338 entity_pb.Property.GD_RATING: Rating, |
1623 entity_pb.Property.GD_RATING: Rating, |
1339 entity_pb.Property.BLOB: Blob, |
1624 entity_pb.Property.BLOB: Blob, |
1340 entity_pb.Property.BYTESTRING: ByteString, |
1625 entity_pb.Property.BYTESTRING: ByteString, |
1341 entity_pb.Property.TEXT: Text, |
1626 entity_pb.Property.TEXT: Text, |
|
1627 entity_pb.Property.BLOBKEY: BlobKey, |
1342 } |
1628 } |
1343 |
1629 |
1344 |
1630 |
1345 def FromPropertyPb(pb): |
1631 def FromPropertyPb(pb): |
1346 """Converts a property PB to a python value. |
1632 """Converts a property PB to a python value. |
1366 elif pbval.has_doublevalue(): |
1652 elif pbval.has_doublevalue(): |
1367 value = pbval.doublevalue() |
1653 value = pbval.doublevalue() |
1368 elif pbval.has_referencevalue(): |
1654 elif pbval.has_referencevalue(): |
1369 value = FromReferenceProperty(pbval) |
1655 value = FromReferenceProperty(pbval) |
1370 elif pbval.has_pointvalue(): |
1656 elif pbval.has_pointvalue(): |
1371 value = (pbval.pointvalue().x(), pbval.pointvalue().y()) |
1657 value = GeoPt(pbval.pointvalue().x(), pbval.pointvalue().y()) |
1372 elif pbval.has_uservalue(): |
1658 elif pbval.has_uservalue(): |
1373 email = unicode(pbval.uservalue().email().decode('utf-8')) |
1659 email = unicode(pbval.uservalue().email().decode('utf-8')) |
1374 auth_domain = unicode(pbval.uservalue().auth_domain().decode('utf-8')) |
1660 auth_domain = unicode(pbval.uservalue().auth_domain().decode('utf-8')) |
1375 obfuscated_gaiaid = pbval.uservalue().obfuscated_gaiaid().decode('utf-8') |
1661 obfuscated_gaiaid = pbval.uservalue().obfuscated_gaiaid().decode('utf-8') |
1376 obfuscated_gaiaid = unicode(obfuscated_gaiaid) |
1662 obfuscated_gaiaid = unicode(obfuscated_gaiaid) |