This repository was archived by the owner on Dec 25, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 39
Expand file tree
/
Copy pathkey.py
More file actions
853 lines (726 loc) · 30.3 KB
/
key.py
File metadata and controls
853 lines (726 loc) · 30.3 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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
#
# Copyright 2008 The ndb Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The Key class, and associated utilities.
A Key encapsulates the following pieces of information, which together
uniquely designate a (possible) entity in the App Engine datastore:
- an application id (a string)
- a namespace (a string)
- a list of one or more (kind, id) pairs where kind is a string and id
is either a string or an integer.
The application id must always be part of the key, but since most
applications can only access their own entities, it defaults to the
current application id and you rarely need to worry about it. It must
not be empty.
The namespace designates a top-level partition of the key space for a
particular application. If you've never heard of namespaces, you can
safely ignore this feature.
Most of the action is in the (kind, id) pairs. A key must have at
least one (kind, id) pair. The last (kind, id) pair gives the kind
and the id of the entity that the key refers to, the others merely
specify a 'parent key'.
The kind is a string giving the name of the model class used to
represent the entity. (In more traditional databases this would be
the table name.) A model class is a Python class derived from
ndb.Model; see the documentation for ndb/model.py. Only the class
name itself is used as the kind. This means all your model classes
must be uniquely named within one application. You can override this
on a per-class basis.
The id is either a string or an integer. When the id is a string, the
application is in control of how it assigns ids: For example, if you
could use an email address as the id for Account entities.
To use integer ids, you must let the datastore choose a unique id for
an entity when it is first inserted into the datastore. You can set
the id to None to represent the key for an entity that hasn't yet been
inserted into the datastore. The final key (including the assigned
id) will be returned after the entity is successfully inserted into
the datastore.
A key for which the id of the last (kind, id) pair is set to None is
called an incomplete key. Such keys can only be used to insert
entities into the datastore.
A key with exactly one (kind, id) pair is called a top level key or a
root key. Top level keys are also used as entity groups, which play a
role in transaction management.
If there is more than one (kind, id) pair, all but the last pair
represent the 'ancestor path', also known as the key of the 'parent
entity'.
Other constraints:
- Kinds and string ids must not be empty and must be at most 500 bytes
long (after UTF-8 encoding, if given as Python unicode objects).
NOTE: This is defined as a module level constant _MAX_KEYPART_BYTES.
- Integer ids must be at least 1 and less than 2**63.
For more info about namespaces, see
http://code.google.com/appengine/docs/python/multitenancy/overview.html.
The namespace defaults to the 'default namespace' selected by the
namespace manager. To explicitly select the empty namespace pass
namespace=''.
"""
__author__ = 'guido@google.com (Guido van Rossum)'
import base64
import os
from .google_imports import datastore_errors
from .google_imports import datastore_types
from .google_imports import namespace_manager
from .google_imports import entity_pb
from . import utils
__all__ = ['Key']
_MAX_LONG = 2L ** 63 # Use 2L, see issue 65. http://goo.gl/ELczz
_MAX_KEYPART_BYTES = 500
class Key(object):
"""An immutable datastore key.
For flexibility and convenience, multiple constructor signatures are
supported.
The primary way to construct a key is using positional arguments:
- Key(kind1, id1, kind2, id2, ...).
This is shorthand for either of the following two longer forms:
- Key(pairs=[(kind1, id1), (kind2, id2), ...])
- Key(flat=[kind1, id1, kind2, id2, ...])
Either of the above constructor forms can additionally pass in another
key using parent=<key>. The (kind, id) pairs of the parent key are
inserted before the (kind, id) pairs passed explicitly.
You can also construct a Key from a 'url-safe' encoded string:
- Key(urlsafe=<string>)
For esoteric purposes the following constructors exist:
- Key(reference=<reference>) -- passing in a low-level Reference object
- Key(serialized=<string>) -- passing in a serialized low-level Reference
- Key(<dict>) -- for unpickling, the same as Key(**<dict>)
The 'url-safe' string is really a websafe-base64-encoded serialized
Reference, but it's best to think of it as just an opaque unique
string.
Additional constructor keyword arguments:
- app=<string> -- specify the application id
- namespace=<string> -- specify the namespace
If a Reference is passed (using one of reference, serialized or
urlsafe), the args and namespace keywords must match what is already
present in the Reference (after decoding if necessary). The parent
keyword cannot be combined with a Reference in any form.
Keys are immutable, which means that a Key object cannot be modified
once it has been created. This is enforced by the implementation as
well as Python allows.
For access to the contents of a key, the following methods and
operations are supported:
- repr(key), str(key) -- return a string representation resembling
the shortest constructor form, omitting the app and namespace
unless they differ from the default value.
- key1 == key2, key1 != key2 -- comparison for equality between Keys.
- hash(key) -- a hash value sufficient for storing Keys in a dict.
- key.pairs() -- a tuple of (kind, id) pairs.
- key.flat() -- a tuple of flattened kind and id values, i.e.
(kind1, id1, kind2, id2, ...).
- key.app() -- the application id.
- key.id() -- the string or integer id in the last (kind, id) pair,
or None if the key is incomplete.
- key.string_id() -- the string id in the last (kind, id) pair,
or None if the key has an integer id or is incomplete.
- key.integer_id() -- the integer id in the last (kind, id) pair,
or None if the key has a string id or is incomplete.
- key.namespace() -- the namespace.
- key.kind() -- a shortcut for key.pairs()[-1][0].
- key.parent() -- a Key constructed from all but the last (kind, id)
pairs.
- key.urlsafe() -- a websafe-base64-encoded serialized Reference.
- key.serialized() -- a serialized Reference.
- key.reference() -- a Reference object. The caller promises not to
mutate it.
Keys also support interaction with the datastore; these methods are
the only ones that engage in any kind of I/O activity. For Future
objects, see the document for ndb/tasklets.py.
- key.get() -- return the entity for the Key.
- key.get_async() -- return a Future whose eventual result is
the entity for the Key.
- key.delete() -- delete the entity for the Key.
- key.delete_async() -- asynchronously delete the entity for the Key.
Keys may be pickled.
Subclassing Key is best avoided; it would be hard to get right.
"""
__slots__ = ['__reference', '__pairs', '__app', '__namespace']
def __new__(cls, *_args, **kwargs):
"""Constructor. See the class docstring for arguments."""
if _args:
if len(_args) == 1 and isinstance(_args[0], dict):
if kwargs:
raise TypeError('Key() takes no keyword arguments when a dict is the '
'the first and only non-keyword argument (for '
'unpickling).')
kwargs = _args[0]
else:
if 'flat' in kwargs:
raise TypeError('Key() with positional arguments '
'cannot accept flat as a keyword argument.')
kwargs['flat'] = _args
self = super(Key, cls).__new__(cls)
# Either __reference or (__pairs, __app, __namespace) must be set.
# Either one fully specifies a key; if both are set they must be
# consistent with each other.
if 'reference' in kwargs or 'serialized' in kwargs or 'urlsafe' in kwargs:
(self.__reference,
self.__pairs,
self.__app,
self.__namespace) = self._parse_from_ref(cls, **kwargs)
elif 'pairs' in kwargs or 'flat' in kwargs:
self.__reference = None
(self.__pairs,
self.__app,
self.__namespace) = self._parse_from_args(**kwargs)
else:
raise TypeError('Key() cannot create a Key instance without arguments.')
return self
@staticmethod
def _parse_from_args(pairs=None, flat=None, app=None, namespace=None,
parent=None):
if flat:
if pairs is not None:
raise TypeError('Key() cannot accept both flat and pairs arguments.')
if len(flat) % 2:
raise ValueError('Key() must have an even number of positional '
'arguments.')
pairs = [(flat[i], flat[i + 1]) for i in xrange(0, len(flat), 2)]
else:
pairs = list(pairs)
if not pairs:
raise TypeError('Key must consist of at least one pair.')
for i, (kind, id) in enumerate(pairs):
if isinstance(id, unicode):
id = id.encode('utf8')
elif id is None:
if i + 1 < len(pairs):
raise datastore_errors.BadArgumentError(
'Incomplete Key entry must be last')
else:
if not isinstance(id, (int, long, str)):
raise TypeError('Key id must be a string or a number; received %r' %
id)
if isinstance(kind, type):
kind = kind._get_kind()
if isinstance(kind, unicode):
kind = kind.encode('utf8')
if not isinstance(kind, str):
raise TypeError('Key kind must be a string or Model class; '
'received %r' % kind)
if not id:
id = None
pairs[i] = (kind, id)
if parent is not None:
if not isinstance(parent, Key):
raise datastore_errors.BadValueError(
'Expected Key instance, got %r' % parent)
if not parent.id():
raise datastore_errors.BadArgumentError(
'Parent cannot have incomplete key')
pairs[:0] = parent.pairs()
if app:
if app != parent.app():
raise ValueError('Cannot specify a different app %r '
'than the parent app %r' %
(app, parent.app()))
else:
app = parent.app()
if namespace is not None:
if namespace != parent.namespace():
raise ValueError('Cannot specify a different namespace %r '
'than the parent namespace %r' %
(namespace, parent.namespace()))
else:
namespace = parent.namespace()
if not app:
app = _DefaultAppId()
if namespace is None:
namespace = _DefaultNamespace()
return tuple(pairs), app, namespace
@staticmethod
def _parse_from_ref(cls, pairs=None, flat=None,
reference=None, serialized=None, urlsafe=None,
app=None, namespace=None, parent=None):
"""Construct a Reference; the signature is the same as for Key."""
if cls is not Key:
raise TypeError('Cannot construct Key reference on non-Key class; '
'received %r' % cls)
if (bool(pairs) + bool(flat) + bool(reference) + bool(serialized) +
bool(urlsafe) + bool(parent)) != 1:
raise TypeError('Cannot construct Key reference from incompatible '
'keyword arguments.')
if urlsafe:
serialized = _DecodeUrlSafe(urlsafe)
if serialized:
reference = _ReferenceFromSerialized(serialized)
if reference:
reference = _ReferenceFromReference(reference)
pairs = []
elem = None
path = reference.path()
for elem in path.element_list():
kind = elem.type()
if elem.has_id():
id_or_name = elem.id()
else:
id_or_name = elem.name()
if not id_or_name:
id_or_name = None
tup = (kind, id_or_name)
pairs.append(tup)
if elem is None:
raise RuntimeError('Key reference has no path or elements (%r, %r, %r).'
% (urlsafe, serialized, str(reference)))
# TODO: ensure that each element has a type and either an id or a name
# You needn't specify app= or namespace= together with reference=,
# serialized= or urlsafe=, but if you do, their values must match
# what is already in the reference.
ref_app = reference.app()
if app is not None:
if app != ref_app:
raise RuntimeError('Key reference constructed uses a different app %r '
'than the one specified %r' %
(ref_app, app))
ref_namespace = reference.name_space()
if namespace is not None:
if namespace != ref_namespace:
raise RuntimeError('Key reference constructed uses a different '
'namespace %r than the one specified %r' %
(ref_namespace, namespace))
return (reference, tuple(pairs), ref_app, ref_namespace)
def __repr__(self):
"""String representation, used by str() and repr().
We produce a short string that conveys all relevant information,
suppressing app and namespace when they are equal to the default.
"""
# TODO: Instead of "Key('Foo', 1)" perhaps return "Key(Foo, 1)" ?
args = []
for item in self.flat():
if not item:
args.append('None')
elif isinstance(item, basestring):
if not isinstance(item, str):
raise TypeError('Key item is not an 8-bit string %r' % item)
args.append(repr(item))
else:
args.append(str(item))
if self.app() != _DefaultAppId():
args.append('app=%r' % self.app())
if self.namespace() != _DefaultNamespace():
args.append('namespace=%r' % self.namespace())
return 'Key(%s)' % ', '.join(args)
__str__ = __repr__
def __hash__(self):
"""Hash value, for use in dict lookups."""
# This ignores app and namespace, which is fine since hash()
# doesn't need to return a unique value -- it only needs to ensure
# that the hashes of equal keys are equal, not the other way
# around.
return hash(tuple(self.pairs()))
def __eq__(self, other):
"""Equality comparison operation."""
# This does not use __tuple() because it is usually enough to
# compare pairs(), and we're performance-conscious here.
if not isinstance(other, Key):
return NotImplemented
return (self.__pairs == other.__pairs and
self.__app == other.__app and
self.__namespace == other.__namespace)
def __ne__(self, other):
"""The opposite of __eq__."""
if not isinstance(other, Key):
return NotImplemented
return not self.__eq__(other)
def __tuple(self):
"""Helper to return an orderable tuple."""
return (self.__app, self.__namespace, self.__pairs)
def __lt__(self, other):
"""Less than ordering."""
if not isinstance(other, Key):
return NotImplemented
return self.__tuple() < other.__tuple()
def __le__(self, other):
"""Less than or equal ordering."""
if not isinstance(other, Key):
return NotImplemented
return self.__tuple() <= other.__tuple()
def __gt__(self, other):
"""Greater than ordering."""
if not isinstance(other, Key):
return NotImplemented
return self.__tuple() > other.__tuple()
def __ge__(self, other):
"""Greater than or equal ordering."""
if not isinstance(other, Key):
return NotImplemented
return self.__tuple() >= other.__tuple()
def __getstate__(self):
"""Private API used for pickling."""
# If any changes are made to this function pickle compatibility tests should
# be updated.
return ({'pairs': self.__pairs,
'app': self.__app,
'namespace': self.__namespace},)
def __setstate__(self, state):
"""Private API used for pickling."""
if len(state) != 1:
raise TypeError('Invalid state length, expected 1; received %i' %
len(state))
kwargs = state[0]
if not isinstance(kwargs, dict):
raise TypeError('Key accepts a dict of keyword arguments as state; '
'received %r' % kwargs)
self.__reference = None
self.__pairs = tuple(kwargs['pairs'])
self.__app = kwargs['app']
self.__namespace = kwargs['namespace']
def __getnewargs__(self):
"""Private API used for pickling."""
return ({'pairs': self.__pairs,
'app': self.__app,
'namespace': self.__namespace},)
def parent(self):
"""Return a Key constructed from all but the last (kind, id) pairs.
If there is only one (kind, id) pair, return None.
"""
pairs = self.__pairs
if len(pairs) <= 1:
return None
return Key(pairs=pairs[:-1], app=self.__app, namespace=self.__namespace)
def root(self):
"""Return the root key. This is either self or the highest parent."""
pairs = self.__pairs
if len(pairs) <= 1:
return self
return Key(pairs=pairs[:1], app=self.__app, namespace=self.__namespace)
def namespace(self):
"""Return the namespace."""
return self.__namespace
def app(self):
"""Return the application id."""
return self.__app
def id(self):
"""Return the string or integer id in the last (kind, id) pair, if any.
Returns:
A string or integer id, or None if the key is incomplete.
"""
return self.__pairs[-1][1]
def string_id(self):
"""Return the string id in the last (kind, id) pair, if any.
Returns:
A string id, or None if the key has an integer id or is incomplete.
"""
id = self.id()
if not isinstance(id, basestring):
id = None
return id
def integer_id(self):
"""Return the integer id in the last (kind, id) pair, if any.
Returns:
An integer id, or None if the key has a string id or is incomplete.
"""
id = self.id()
if not isinstance(id, (int, long)):
id = None
return id
def pairs(self):
"""Return a tuple of (kind, id) pairs."""
return self.__pairs
def flat(self):
"""Return a tuple of alternating kind and id values."""
flat = []
for kind, id in self.__pairs:
flat.append(kind)
flat.append(id)
return tuple(flat)
def kind(self):
"""Return the kind of the entity referenced.
This is the kind from the last (kind, id) pair.
"""
return self.__pairs[-1][0]
def reference(self):
"""Return the Reference object for this Key.
This is a entity_pb.Reference instance -- a protocol buffer class
used by the lower-level API to the datastore.
NOTE: The caller should not mutate the return value.
"""
if self.__reference is None:
self.__reference = _ConstructReference(self.__class__,
pairs=self.__pairs,
app=self.__app,
namespace=self.__namespace)
return self.__reference
def serialized(self):
"""Return a serialized Reference object for this Key."""
return self.reference().Encode()
def urlsafe(self):
"""Return a url-safe string encoding this Key's Reference.
This string is compatible with other APIs and languages and with
the strings used to represent Keys in GQL and in the App Engine
Admin Console.
"""
# This is 3-4x faster than urlsafe_b64decode()
urlsafe = base64.b64encode(self.reference().Encode())
return urlsafe.rstrip('=').replace('+', '-').replace('/', '_')
# Datastore API using the default context.
# These use local import since otherwise they'd be recursive imports.
def get(self, **ctx_options):
"""Synchronously get the entity for this Key.
Return None if there is no such entity.
"""
return self.get_async(**ctx_options).get_result()
def get_async(self, **ctx_options):
"""Return a Future whose result is the entity for this Key.
If no such entity exists, a Future is still returned, and the
Future's eventual return result be None.
"""
from . import model, tasklets
ctx = tasklets.get_context()
cls = model.Model._kind_map.get(self.kind())
if cls:
cls._pre_get_hook(self)
fut = ctx.get(self, **ctx_options)
if cls:
post_hook = cls._post_get_hook
if not cls._is_default_hook(model.Model._default_post_get_hook,
post_hook):
fut.add_immediate_callback(post_hook, self, fut)
return fut
def delete(self, **ctx_options):
"""Synchronously delete the entity for this Key.
This is a no-op if no such entity exists.
"""
return self.delete_async(**ctx_options).get_result()
def delete_async(self, **ctx_options):
"""Schedule deletion of the entity for this Key.
This returns a Future, whose result becomes available once the
deletion is complete. If no such entity exists, a Future is still
returned. In all cases the Future's result is None (i.e. there is
no way to tell whether the entity existed or not).
"""
from . import tasklets, model
ctx = tasklets.get_context()
cls = model.Model._kind_map.get(self.kind())
if cls:
cls._pre_delete_hook(self)
fut = ctx.delete(self, **ctx_options)
if cls:
post_hook = cls._post_delete_hook
if not cls._is_default_hook(model.Model._default_post_delete_hook,
post_hook):
fut.add_immediate_callback(post_hook, self, fut)
return fut
@classmethod
def from_old_key(cls, old_key):
return cls(urlsafe=str(old_key))
def to_old_key(self):
return datastore_types.Key(encoded=self.urlsafe())
# The remaining functions in this module are private.
# TODO: Conform to PEP 8 naming, e.g. _construct_reference() etc.
@utils.positional(1)
def _ConstructReference(cls, pairs=None, flat=None,
reference=None, serialized=None, urlsafe=None,
app=None, namespace=None, parent=None):
"""Construct a Reference; the signature is the same as for Key."""
if cls is not Key:
raise TypeError('Cannot construct Key reference on non-Key class; '
'received %r' % cls)
if (bool(pairs) + bool(flat) + bool(reference) + bool(serialized) +
bool(urlsafe)) != 1:
raise TypeError('Cannot construct Key reference from incompatible keyword '
'arguments.')
if flat or pairs:
if flat:
if len(flat) % 2:
raise TypeError('_ConstructReference() must have an even number of '
'positional arguments.')
pairs = [(flat[i], flat[i + 1]) for i in xrange(0, len(flat), 2)]
elif parent is not None:
pairs = list(pairs)
if not pairs:
raise TypeError('Key references must consist of at least one pair.')
if parent is not None:
if not isinstance(parent, Key):
raise datastore_errors.BadValueError(
'Expected Key instance, got %r' % parent)
pairs[:0] = parent.pairs()
if app:
if app != parent.app():
raise ValueError('Cannot specify a different app %r '
'than the parent app %r' %
(app, parent.app()))
else:
app = parent.app()
if namespace is not None:
if namespace != parent.namespace():
raise ValueError('Cannot specify a different namespace %r '
'than the parent namespace %r' %
(namespace, parent.namespace()))
else:
namespace = parent.namespace()
reference = _ReferenceFromPairs(pairs, app=app, namespace=namespace)
else:
if parent is not None:
raise TypeError('Key reference cannot be constructed when the parent '
'argument is combined with either reference, serialized '
'or urlsafe arguments.')
if urlsafe:
serialized = _DecodeUrlSafe(urlsafe)
if serialized:
reference = _ReferenceFromSerialized(serialized)
if not reference.path().element_size():
raise RuntimeError('Key reference has no path or elements (%r, %r, %r).'
% (urlsafe, serialized, str(reference)))
# TODO: ensure that each element has a type and either an id or a name
if not serialized:
reference = _ReferenceFromReference(reference)
# You needn't specify app= or namespace= together with reference=,
# serialized= or urlsafe=, but if you do, their values must match
# what is already in the reference.
if app is not None:
ref_app = reference.app()
if app != ref_app:
raise RuntimeError('Key reference constructed uses a different app %r '
'than the one specified %r' %
(ref_app, app))
if namespace is not None:
ref_namespace = reference.name_space()
if namespace != ref_namespace:
raise RuntimeError('Key reference constructed uses a different '
'namespace %r than the one specified %r' %
(ref_namespace, namespace))
return reference
def _ReferenceFromPairs(pairs, reference=None, app=None, namespace=None):
"""Construct a Reference from a list of pairs.
If a Reference is passed in as the second argument, it is modified
in place. The app and namespace are set from the corresponding
keyword arguments, with the customary defaults.
"""
if reference is None:
reference = entity_pb.Reference()
path = reference.mutable_path()
last = False
for kind, idorname in pairs:
if last:
raise datastore_errors.BadArgumentError(
'Incomplete Key entry must be last')
t = type(kind)
if t is str:
pass
elif t is unicode:
kind = kind.encode('utf8')
else:
if issubclass(t, type):
# Late import to avoid cycles.
from .model import Model
modelclass = kind
if not issubclass(modelclass, Model):
raise TypeError('Key kind must be either a string or subclass of '
'Model; received %r' % modelclass)
kind = modelclass._get_kind()
t = type(kind)
if t is str:
pass
elif t is unicode:
kind = kind.encode('utf8')
elif issubclass(t, str):
pass
elif issubclass(t, unicode):
kind = kind.encode('utf8')
else:
raise TypeError('Key kind must be either a string or subclass of Model;'
' received %r' % kind)
# pylint: disable=superfluous-parens
if not (1 <= len(kind) <= _MAX_KEYPART_BYTES):
raise ValueError('Key kind string must be a non-empty string up to %i'
'bytes; received %s' %
(_MAX_KEYPART_BYTES, kind))
elem = path.add_element()
elem.set_type(kind)
t = type(idorname)
if t is int or t is long:
# pylint: disable=superfluous-parens
if not (1 <= idorname < _MAX_LONG):
raise ValueError('Key id number is too long; received %i' % idorname)
elem.set_id(idorname)
elif t is str:
# pylint: disable=superfluous-parens
if not (1 <= len(idorname) <= _MAX_KEYPART_BYTES):
raise ValueError('Key name strings must be non-empty strings up to %i '
'bytes; received %s' %
(_MAX_KEYPART_BYTES, idorname))
elem.set_name(idorname)
elif t is unicode:
idorname = idorname.encode('utf8')
# pylint: disable=superfluous-parens
if not (1 <= len(idorname) <= _MAX_KEYPART_BYTES):
raise ValueError('Key name unicode strings must be non-empty strings up'
' to %i bytes; received %s' %
(_MAX_KEYPART_BYTES, idorname))
elem.set_name(idorname)
elif idorname is None:
last = True
elif issubclass(t, (int, long)):
# pylint: disable=superfluous-parens
if not (1 <= idorname < _MAX_LONG):
raise ValueError('Key id number is too long; received %i' % idorname)
elem.set_id(idorname)
elif issubclass(t, basestring):
if issubclass(t, unicode):
idorname = idorname.encode('utf8')
# pylint: disable=superfluous-parens
if not (1 <= len(idorname) <= _MAX_KEYPART_BYTES):
raise ValueError('Key name strings must be non-empty strings up to %i '
'bytes; received %s' % (_MAX_KEYPART_BYTES, idorname))
elem.set_name(idorname)
else:
raise TypeError('id must be either a numeric id or a string name; '
'received %r' % idorname)
# An empty app id means to use the default app id.
if not app:
app = _DefaultAppId()
# Always set the app id, since it is mandatory.
reference.set_app(app)
# An empty namespace overrides the default namespace.
if namespace is None:
namespace = _DefaultNamespace()
# Only set the namespace if it is not empty.
if namespace:
reference.set_name_space(namespace)
return reference
def _ReferenceFromReference(reference):
"""Copy a Reference."""
new_reference = entity_pb.Reference()
new_reference.CopyFrom(reference)
return new_reference
def _ReferenceFromSerialized(serialized):
"""Construct a Reference from a serialized Reference."""
if not isinstance(serialized, basestring):
raise TypeError('serialized must be a string; received %r' % serialized)
elif isinstance(serialized, unicode):
serialized = serialized.encode('utf8')
return entity_pb.Reference(serialized)
def _DecodeUrlSafe(urlsafe):
"""Decode a url-safe base64-encoded string.
This returns the decoded string.
"""
if not isinstance(urlsafe, basestring):
raise TypeError('urlsafe must be a string; received %r' % urlsafe)
if isinstance(urlsafe, unicode):
urlsafe = urlsafe.encode('utf8')
mod = len(urlsafe) % 4
if mod:
urlsafe += '=' * (4 - mod)
# This is 3-4x faster than urlsafe_b64decode()
return base64.b64decode(urlsafe.replace('-', '+').replace('_', '/'))
def _DefaultAppId():
"""Return the default application id.
This is taken from the APPLICATION_ID environment variable.
"""
return os.getenv('APPLICATION_ID', '_')
def _DefaultNamespace():
"""Return the default namespace.
This is taken from the namespace manager.
"""
return namespace_manager.get_namespace()