1# Copyright 2014 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Convenience wrapper for invoking APIs/factories w/ a project.""" 15 16import os 17import warnings 18 19import google.api_core.client_options 20from google.auth.credentials import AnonymousCredentials # type: ignore 21from google.cloud._helpers import _LocalStack # type: ignore 22from google.cloud._helpers import _determine_default_project as _base_default_project # type: ignore 23from google.cloud.client import ClientWithProject # type: ignore 24from google.cloud.datastore.version import __version__ 25from google.cloud.datastore import helpers 26from google.cloud.datastore._http import HTTPDatastoreAPI 27from google.cloud.datastore.batch import Batch 28from google.cloud.datastore.entity import Entity 29from google.cloud.datastore.key import Key 30from google.cloud.datastore.query import Query 31from google.cloud.datastore.transaction import Transaction 32 33try: 34 from google.cloud.datastore._gapic import make_datastore_api 35except ImportError: # pragma: NO COVER 36 from google.api_core import client_info as api_core_client_info 37 38 def make_datastore_api(client): 39 raise RuntimeError("No gRPC available") 40 41 _HAVE_GRPC = False 42 _CLIENT_INFO = api_core_client_info.ClientInfo(client_library_version=__version__) 43else: 44 from google.api_core.gapic_v1 import client_info 45 46 _HAVE_GRPC = True 47 _CLIENT_INFO = client_info.ClientInfo( 48 client_library_version=__version__, gapic_version=__version__ 49 ) 50 51 52_MAX_LOOPS = 128 53"""Maximum number of iterations to wait for deferred keys.""" 54_DATASTORE_BASE_URL = "https://datastore.googleapis.com" 55"""Datastore API request URL base.""" 56 57DATASTORE_EMULATOR_HOST = "DATASTORE_EMULATOR_HOST" 58"""Environment variable defining host for datastore emulator server.""" 59DATASTORE_DATASET = "DATASTORE_DATASET" 60"""Environment variable defining default dataset ID under GCD.""" 61DISABLE_GRPC = "GOOGLE_CLOUD_DISABLE_GRPC" 62"""Environment variable acting as flag to disable gRPC.""" 63 64_RESERVE_IDS_DEPRECATED_MESSAGE = """\ 65Client.reserve_ids is deprecated. Please use \ 66Client.reserve_ids_multi or Client.reserve_ids_sequential""" 67 68 69_USE_GRPC = _HAVE_GRPC and not os.getenv(DISABLE_GRPC, False) 70 71 72def _get_gcd_project(): 73 """Gets the GCD application ID if it can be inferred.""" 74 return os.getenv(DATASTORE_DATASET) 75 76 77def _determine_default_project(project=None): 78 """Determine default project explicitly or implicitly as fall-back. 79 80 In implicit case, supports four environments. In order of precedence, the 81 implicit environments are: 82 83 * DATASTORE_DATASET environment variable (for ``gcd`` / emulator testing) 84 * GOOGLE_CLOUD_PROJECT environment variable 85 * Google App Engine application ID 86 * Google Compute Engine project ID (from metadata server) 87 88 :type project: str 89 :param project: Optional. The project to use as default. 90 91 :rtype: str or ``NoneType`` 92 :returns: Default project if it can be determined. 93 """ 94 if project is None: 95 project = _get_gcd_project() 96 97 if project is None: 98 project = _base_default_project(project=project) 99 100 return project 101 102 103def _make_retry_timeout_kwargs(retry, timeout): 104 """Helper: make optional retry / timeout kwargs dict.""" 105 kwargs = {} 106 107 if retry is not None: 108 kwargs["retry"] = retry 109 110 if timeout is not None: 111 kwargs["timeout"] = timeout 112 113 return kwargs 114 115 116def _extended_lookup( 117 datastore_api, 118 project, 119 key_pbs, 120 missing=None, 121 deferred=None, 122 eventual=False, 123 transaction_id=None, 124 retry=None, 125 timeout=None, 126): 127 """Repeat lookup until all keys found (unless stop requested). 128 129 Helper function for :meth:`Client.get_multi`. 130 131 :type datastore_api: 132 :class:`google.cloud.datastore._http.HTTPDatastoreAPI` 133 or :class:`google.cloud.datastore_v1.gapic.DatastoreClient` 134 :param datastore_api: The datastore API object used to connect 135 to datastore. 136 137 :type project: str 138 :param project: The project to make the request for. 139 140 :type key_pbs: list of :class:`.entity_pb2.Key` 141 :param key_pbs: The keys to retrieve from the datastore. 142 143 :type missing: list 144 :param missing: (Optional) If a list is passed, the key-only entity 145 protobufs returned by the backend as "missing" will be 146 copied into it. 147 148 :type deferred: list 149 :param deferred: (Optional) If a list is passed, the key protobufs returned 150 by the backend as "deferred" will be copied into it. 151 152 :type eventual: bool 153 :param eventual: If False (the default), request ``STRONG`` read 154 consistency. If True, request ``EVENTUAL`` read 155 consistency. 156 157 :type transaction_id: str 158 :param transaction_id: If passed, make the request in the scope of 159 the given transaction. Incompatible with 160 ``eventual==True``. 161 162 :type retry: :class:`google.api_core.retry.Retry` 163 :param retry: 164 A retry object used to retry requests. If ``None`` is specified, 165 requests will be retried using a default configuration. 166 167 :type timeout: float 168 :param timeout: 169 Time, in seconds, to wait for the request to complete. 170 Note that if ``retry`` is specified, the timeout applies 171 to each individual attempt. 172 173 :rtype: list of :class:`.entity_pb2.Entity` 174 :returns: The requested entities. 175 :raises: :class:`ValueError` if missing / deferred are not null or 176 empty list. 177 """ 178 if missing is not None and missing != []: 179 raise ValueError("missing must be None or an empty list") 180 181 if deferred is not None and deferred != []: 182 raise ValueError("deferred must be None or an empty list") 183 184 kwargs = _make_retry_timeout_kwargs(retry, timeout) 185 186 results = [] 187 188 loop_num = 0 189 read_options = helpers.get_read_options(eventual, transaction_id) 190 while loop_num < _MAX_LOOPS: # loop against possible deferred. 191 loop_num += 1 192 lookup_response = datastore_api.lookup( 193 request={ 194 "project_id": project, 195 "keys": key_pbs, 196 "read_options": read_options, 197 }, 198 **kwargs, 199 ) 200 201 # Accumulate the new results. 202 results.extend(result.entity for result in lookup_response.found) 203 204 if missing is not None: 205 missing.extend(result.entity for result in lookup_response.missing) 206 207 if deferred is not None: 208 deferred.extend(lookup_response.deferred) 209 break 210 211 if len(lookup_response.deferred) == 0: 212 break 213 214 # We have deferred keys, and the user didn't ask to know about 215 # them, so retry (but only with the deferred ones). 216 key_pbs = lookup_response.deferred 217 218 return results 219 220 221class Client(ClientWithProject): 222 """Convenience wrapper for invoking APIs/factories w/ a project. 223 224 .. doctest:: 225 226 >>> from google.cloud import datastore 227 >>> client = datastore.Client() 228 229 :type project: str 230 :param project: (Optional) The project to pass to proxied API methods. 231 232 :type namespace: str 233 :param namespace: (Optional) namespace to pass to proxied API methods. 234 235 :type credentials: :class:`~google.auth.credentials.Credentials` 236 :param credentials: (Optional) The OAuth2 Credentials to use for this 237 client. If not passed (and if no ``_http`` object is 238 passed), falls back to the default inferred from the 239 environment. 240 241 :type client_info: :class:`google.api_core.gapic_v1.client_info.ClientInfo` 242 or :class:`google.api_core.client_info.ClientInfo` 243 :param client_info: (Optional) The client info used to send a user-agent 244 string along with API requests. If ``None``, then 245 default info will be used. Generally, 246 you only need to set this if you're developing your 247 own library or partner tool. 248 249 :type client_options: :class:`~google.api_core.client_options.ClientOptions` 250 or :class:`dict` 251 :param client_options: (Optional) Client options used to set user options on the 252 client. API Endpoint should be set through client_options. 253 254 :type _http: :class:`~requests.Session` 255 :param _http: (Optional) HTTP object to make requests. Can be any object 256 that defines ``request()`` with the same interface as 257 :meth:`requests.Session.request`. If not passed, an 258 ``_http`` object is created that is bound to the 259 ``credentials`` for the current object. 260 This parameter should be considered private, and could 261 change in the future. 262 263 :type _use_grpc: bool 264 :param _use_grpc: (Optional) Explicitly specifies whether 265 to use the gRPC transport (via GAX) or HTTP. If unset, 266 falls back to the ``GOOGLE_CLOUD_DISABLE_GRPC`` 267 environment variable. 268 This parameter should be considered private, and could 269 change in the future. 270 """ 271 272 SCOPE = ("https://www.googleapis.com/auth/datastore",) 273 """The scopes required for authenticating as a Cloud Datastore consumer.""" 274 275 def __init__( 276 self, 277 project=None, 278 namespace=None, 279 credentials=None, 280 client_info=_CLIENT_INFO, 281 client_options=None, 282 _http=None, 283 _use_grpc=None, 284 ): 285 emulator_host = os.getenv(DATASTORE_EMULATOR_HOST) 286 287 if emulator_host is not None: 288 if credentials is not None: 289 raise ValueError( 290 "Explicit credentials are incompatible with the emulator" 291 ) 292 credentials = AnonymousCredentials() 293 294 super(Client, self).__init__( 295 project=project, 296 credentials=credentials, 297 client_options=client_options, 298 _http=_http, 299 ) 300 self.namespace = namespace 301 self._client_info = client_info 302 self._client_options = client_options 303 self._batch_stack = _LocalStack() 304 self._datastore_api_internal = None 305 306 if _use_grpc is None: 307 self._use_grpc = _USE_GRPC 308 else: 309 self._use_grpc = _use_grpc 310 311 if emulator_host is not None: 312 self._base_url = "http://" + emulator_host 313 else: 314 api_endpoint = _DATASTORE_BASE_URL 315 if client_options: 316 if type(client_options) == dict: 317 client_options = google.api_core.client_options.from_dict( 318 client_options 319 ) 320 if client_options.api_endpoint: 321 api_endpoint = client_options.api_endpoint 322 self._base_url = api_endpoint 323 324 @staticmethod 325 def _determine_default(project): 326 """Helper: override default project detection.""" 327 return _determine_default_project(project) 328 329 @property 330 def base_url(self): 331 """Getter for API base URL.""" 332 return self._base_url 333 334 @base_url.setter 335 def base_url(self, value): 336 """Setter for API base URL.""" 337 self._base_url = value 338 339 @property 340 def _datastore_api(self): 341 """Getter for a wrapped API object.""" 342 if self._datastore_api_internal is None: 343 if self._use_grpc: 344 self._datastore_api_internal = make_datastore_api(self) 345 else: 346 self._datastore_api_internal = HTTPDatastoreAPI(self) 347 return self._datastore_api_internal 348 349 def _push_batch(self, batch): 350 """Push a batch/transaction onto our stack. 351 352 "Protected", intended for use by batch / transaction context mgrs. 353 354 :type batch: :class:`google.cloud.datastore.batch.Batch`, or an object 355 implementing its API. 356 :param batch: newly-active batch/transaction. 357 """ 358 self._batch_stack.push(batch) 359 360 def _pop_batch(self): 361 """Pop a batch/transaction from our stack. 362 363 "Protected", intended for use by batch / transaction context mgrs. 364 365 :raises: IndexError if the stack is empty. 366 :rtype: :class:`google.cloud.datastore.batch.Batch`, or an object 367 implementing its API. 368 :returns: the top-most batch/transaction, after removing it. 369 """ 370 return self._batch_stack.pop() 371 372 @property 373 def current_batch(self): 374 """Currently-active batch. 375 376 :rtype: :class:`google.cloud.datastore.batch.Batch`, or an object 377 implementing its API, or ``NoneType`` (if no batch is active). 378 :returns: The batch/transaction at the top of the batch stack. 379 """ 380 return self._batch_stack.top 381 382 @property 383 def current_transaction(self): 384 """Currently-active transaction. 385 386 :rtype: :class:`google.cloud.datastore.transaction.Transaction`, or an 387 object implementing its API, or ``NoneType`` (if no transaction 388 is active). 389 :returns: The transaction at the top of the batch stack. 390 """ 391 transaction = self.current_batch 392 if isinstance(transaction, Transaction): 393 return transaction 394 395 def get( 396 self, 397 key, 398 missing=None, 399 deferred=None, 400 transaction=None, 401 eventual=False, 402 retry=None, 403 timeout=None, 404 ): 405 """Retrieve an entity from a single key (if it exists). 406 407 .. note:: 408 409 This is just a thin wrapper over :meth:`get_multi`. 410 The backend API does not make a distinction between a single key or 411 multiple keys in a lookup request. 412 413 :type key: :class:`google.cloud.datastore.key.Key` 414 :param key: The key to be retrieved from the datastore. 415 416 :type missing: list 417 :param missing: (Optional) If a list is passed, the key-only entities 418 returned by the backend as "missing" will be copied 419 into it. 420 421 :type deferred: list 422 :param deferred: (Optional) If a list is passed, the keys returned 423 by the backend as "deferred" will be copied into it. 424 425 :type transaction: 426 :class:`~google.cloud.datastore.transaction.Transaction` 427 :param transaction: (Optional) Transaction to use for read consistency. 428 If not passed, uses current transaction, if set. 429 430 :type eventual: bool 431 :param eventual: (Optional) Defaults to strongly consistent (False). 432 Setting True will use eventual consistency, but cannot 433 be used inside a transaction or will raise ValueError. 434 435 :type retry: :class:`google.api_core.retry.Retry` 436 :param retry: 437 A retry object used to retry requests. If ``None`` is specified, 438 requests will be retried using a default configuration. 439 440 :type timeout: float 441 :param timeout: 442 Time, in seconds, to wait for the request to complete. 443 Note that if ``retry`` is specified, the timeout applies 444 to each individual attempt. 445 446 :rtype: :class:`google.cloud.datastore.entity.Entity` or ``NoneType`` 447 :returns: The requested entity if it exists. 448 449 :raises: :class:`ValueError` if eventual is True and in a transaction. 450 """ 451 entities = self.get_multi( 452 keys=[key], 453 missing=missing, 454 deferred=deferred, 455 transaction=transaction, 456 eventual=eventual, 457 retry=retry, 458 timeout=timeout, 459 ) 460 if entities: 461 return entities[0] 462 463 def get_multi( 464 self, 465 keys, 466 missing=None, 467 deferred=None, 468 transaction=None, 469 eventual=False, 470 retry=None, 471 timeout=None, 472 ): 473 """Retrieve entities, along with their attributes. 474 475 :type keys: list of :class:`google.cloud.datastore.key.Key` 476 :param keys: The keys to be retrieved from the datastore. 477 478 :type missing: list 479 :param missing: (Optional) If a list is passed, the key-only entities 480 returned by the backend as "missing" will be copied 481 into it. If the list is not empty, an error will occur. 482 483 :type deferred: list 484 :param deferred: (Optional) If a list is passed, the keys returned 485 by the backend as "deferred" will be copied into it. 486 If the list is not empty, an error will occur. 487 488 :type transaction: 489 :class:`~google.cloud.datastore.transaction.Transaction` 490 :param transaction: (Optional) Transaction to use for read consistency. 491 If not passed, uses current transaction, if set. 492 493 :type eventual: bool 494 :param eventual: (Optional) Defaults to strongly consistent (False). 495 Setting True will use eventual consistency, but cannot 496 be used inside a transaction or will raise ValueError. 497 498 :type retry: :class:`google.api_core.retry.Retry` 499 :param retry: 500 A retry object used to retry requests. If ``None`` is specified, 501 requests will be retried using a default configuration. 502 503 :type timeout: float 504 :param timeout: 505 Time, in seconds, to wait for the request to complete. 506 Note that if ``retry`` is specified, the timeout applies 507 to each individual attempt. 508 509 :rtype: list of :class:`google.cloud.datastore.entity.Entity` 510 :returns: The requested entities. 511 :raises: :class:`ValueError` if one or more of ``keys`` has a project 512 which does not match our project. 513 :raises: :class:`ValueError` if eventual is True and in a transaction. 514 """ 515 if not keys: 516 return [] 517 518 ids = set(key.project for key in keys) 519 for current_id in ids: 520 if current_id != self.project: 521 raise ValueError("Keys do not match project") 522 523 if transaction is None: 524 transaction = self.current_transaction 525 526 entity_pbs = _extended_lookup( 527 datastore_api=self._datastore_api, 528 project=self.project, 529 key_pbs=[key.to_protobuf() for key in keys], 530 eventual=eventual, 531 missing=missing, 532 deferred=deferred, 533 transaction_id=transaction and transaction.id, 534 retry=retry, 535 timeout=timeout, 536 ) 537 538 if missing is not None: 539 missing[:] = [ 540 helpers.entity_from_protobuf(missed_pb) for missed_pb in missing 541 ] 542 543 if deferred is not None: 544 deferred[:] = [ 545 helpers.key_from_protobuf(deferred_pb) for deferred_pb in deferred 546 ] 547 548 return [helpers.entity_from_protobuf(entity_pb._pb) for entity_pb in entity_pbs] 549 550 def put(self, entity, retry=None, timeout=None): 551 """Save an entity in the Cloud Datastore. 552 553 .. note:: 554 555 This is just a thin wrapper over :meth:`put_multi`. 556 The backend API does not make a distinction between a single 557 entity or multiple entities in a commit request. 558 559 :type entity: :class:`google.cloud.datastore.entity.Entity` 560 :param entity: The entity to be saved to the datastore. 561 562 :type retry: :class:`google.api_core.retry.Retry` 563 :param retry: 564 A retry object used to retry requests. If ``None`` is specified, 565 requests will be retried using a default configuration. 566 Only meaningful outside of another batch / transaction. 567 568 :type timeout: float 569 :param timeout: 570 Time, in seconds, to wait for the request to complete. 571 Note that if ``retry`` is specified, the timeout applies 572 to each individual attempt. Only meaningful outside of another 573 batch / transaction. 574 """ 575 self.put_multi(entities=[entity], retry=retry, timeout=timeout) 576 577 def put_multi(self, entities, retry=None, timeout=None): 578 """Save entities in the Cloud Datastore. 579 580 :type entities: list of :class:`google.cloud.datastore.entity.Entity` 581 :param entities: The entities to be saved to the datastore. 582 583 :type retry: :class:`google.api_core.retry.Retry` 584 :param retry: 585 A retry object used to retry requests. If ``None`` is specified, 586 requests will be retried using a default configuration. 587 Only meaningful outside of another batch / transaction. 588 589 :type timeout: float 590 :param timeout: 591 Time, in seconds, to wait for the request to complete. 592 Note that if ``retry`` is specified, the timeout applies 593 to each individual attempt. Only meaningful outside of another 594 batch / transaction. 595 596 :raises: :class:`ValueError` if ``entities`` is a single entity. 597 """ 598 if isinstance(entities, Entity): 599 raise ValueError("Pass a sequence of entities") 600 601 if not entities: 602 return 603 604 current = self.current_batch 605 in_batch = current is not None 606 607 if not in_batch: 608 current = self.batch() 609 current.begin() 610 611 for entity in entities: 612 current.put(entity) 613 614 if not in_batch: 615 current.commit(retry=retry, timeout=timeout) 616 617 def delete(self, key, retry=None, timeout=None): 618 """Delete the key in the Cloud Datastore. 619 620 .. note:: 621 622 This is just a thin wrapper over :meth:`delete_multi`. 623 The backend API does not make a distinction between a single key or 624 multiple keys in a commit request. 625 626 :type key: :class:`google.cloud.datastore.key.Key`, :class:`google.cloud.datastore.entity.Entity` 627 628 :param key: The key to be deleted from the datastore. 629 630 :type retry: :class:`google.api_core.retry.Retry` 631 :param retry: 632 A retry object used to retry requests. If ``None`` is specified, 633 requests will be retried using a default configuration. 634 Only meaningful outside of another batch / transaction. 635 636 :type timeout: float 637 :param timeout: 638 Time, in seconds, to wait for the request to complete. 639 Note that if ``retry`` is specified, the timeout applies 640 to each individual attempt. Only meaningful outside of another 641 batch / transaction. 642 """ 643 self.delete_multi(keys=[key], retry=retry, timeout=timeout) 644 645 def delete_multi(self, keys, retry=None, timeout=None): 646 """Delete keys from the Cloud Datastore. 647 648 :type keys: list of :class:`google.cloud.datastore.key.Key`, :class:`google.cloud.datastore.entity.Entity` 649 :param keys: The keys to be deleted from the Datastore. 650 651 :type retry: :class:`google.api_core.retry.Retry` 652 :param retry: 653 A retry object used to retry requests. If ``None`` is specified, 654 requests will be retried using a default configuration. 655 Only meaningful outside of another batch / transaction. 656 657 :type timeout: float 658 :param timeout: 659 Time, in seconds, to wait for the request to complete. 660 Note that if ``retry`` is specified, the timeout applies 661 to each individual attempt. Only meaningful outside of another 662 batch / transaction. 663 """ 664 if not keys: 665 return 666 667 # We allow partial keys to attempt a delete, the backend will fail. 668 current = self.current_batch 669 in_batch = current is not None 670 671 if not in_batch: 672 current = self.batch() 673 current.begin() 674 675 for key in keys: 676 if isinstance(key, Entity): 677 # If the key is in fact an Entity, the key can be extracted. 678 key = key.key 679 current.delete(key) 680 681 if not in_batch: 682 current.commit(retry=retry, timeout=timeout) 683 684 def allocate_ids(self, incomplete_key, num_ids, retry=None, timeout=None): 685 """Allocate a list of IDs from a partial key. 686 687 :type incomplete_key: :class:`google.cloud.datastore.key.Key` 688 :param incomplete_key: Partial key to use as base for allocated IDs. 689 690 :type num_ids: int 691 :param num_ids: The number of IDs to allocate. 692 693 :type retry: :class:`google.api_core.retry.Retry` 694 :param retry: 695 A retry object used to retry requests. If ``None`` is specified, 696 requests will be retried using a default configuration. 697 698 :type timeout: float 699 :param timeout: 700 Time, in seconds, to wait for the request to complete. 701 Note that if ``retry`` is specified, the timeout applies 702 to each individual attempt. 703 704 :rtype: list of :class:`google.cloud.datastore.key.Key` 705 :returns: The (complete) keys allocated with ``incomplete_key`` as 706 root. 707 :raises: :class:`ValueError` if ``incomplete_key`` is not a 708 partial key. 709 """ 710 if not incomplete_key.is_partial: 711 raise ValueError(("Key is not partial.", incomplete_key)) 712 713 incomplete_key_pb = incomplete_key.to_protobuf() 714 incomplete_key_pbs = [incomplete_key_pb] * num_ids 715 716 kwargs = _make_retry_timeout_kwargs(retry, timeout) 717 718 response_pb = self._datastore_api.allocate_ids( 719 request={"project_id": incomplete_key.project, "keys": incomplete_key_pbs}, 720 **kwargs, 721 ) 722 allocated_ids = [ 723 allocated_key_pb.path[-1].id for allocated_key_pb in response_pb.keys 724 ] 725 return [ 726 incomplete_key.completed_key(allocated_id) for allocated_id in allocated_ids 727 ] 728 729 def key(self, *path_args, **kwargs): 730 """Proxy to :class:`google.cloud.datastore.key.Key`. 731 732 Passes our ``project``. 733 """ 734 if "project" in kwargs: 735 raise TypeError("Cannot pass project") 736 kwargs["project"] = self.project 737 if "namespace" not in kwargs: 738 kwargs["namespace"] = self.namespace 739 return Key(*path_args, **kwargs) 740 741 def entity(self, key=None, exclude_from_indexes=()): 742 """Proxy to :class:`google.cloud.datastore.entity.Entity`.""" 743 return Entity(key=key, exclude_from_indexes=exclude_from_indexes) 744 745 def batch(self): 746 """Proxy to :class:`google.cloud.datastore.batch.Batch`.""" 747 return Batch(self) 748 749 def transaction(self, **kwargs): 750 """Proxy to :class:`google.cloud.datastore.transaction.Transaction`. 751 752 :param kwargs: Keyword arguments to be passed in. 753 """ 754 return Transaction(self, **kwargs) 755 756 def query(self, **kwargs): 757 """Proxy to :class:`google.cloud.datastore.query.Query`. 758 759 Passes our ``project``. 760 761 Using query to search a datastore: 762 763 .. testsetup:: query 764 765 import uuid 766 767 from google.cloud import datastore 768 769 unique = str(uuid.uuid4())[0:8] 770 client = datastore.Client(namespace='ns{}'.format(unique)) 771 772 def do_something_with(entity): 773 pass 774 775 .. doctest:: query 776 777 >>> query = client.query(kind='MyKind') 778 >>> query.add_filter('property', '=', 'val') 779 <google.cloud.datastore.query.Query object at ...> 780 781 Using the query iterator 782 783 .. doctest:: query 784 785 >>> filters = [('property', '=', 'val')] 786 >>> query = client.query(kind='MyKind', filters=filters) 787 >>> query_iter = query.fetch() 788 >>> for entity in query_iter: 789 ... do_something_with(entity) 790 791 or manually page through results 792 793 .. doctest:: query 794 795 >>> query_iter = query.fetch() 796 >>> pages = query_iter.pages 797 >>> 798 >>> first_page = next(pages) 799 >>> first_page_entities = list(first_page) 800 >>> query_iter.next_page_token is None 801 True 802 803 :param kwargs: Parameters for initializing and instance of 804 :class:`~google.cloud.datastore.query.Query`. 805 806 :rtype: :class:`~google.cloud.datastore.query.Query` 807 :returns: A query object. 808 """ 809 if "client" in kwargs: 810 raise TypeError("Cannot pass client") 811 if "project" in kwargs: 812 raise TypeError("Cannot pass project") 813 kwargs["project"] = self.project 814 if "namespace" not in kwargs: 815 kwargs["namespace"] = self.namespace 816 return Query(self, **kwargs) 817 818 def reserve_ids_sequential(self, complete_key, num_ids, retry=None, timeout=None): 819 """Reserve a list of IDs sequentially from a complete key. 820 821 This will reserve the key passed as `complete_key` as well as 822 additional keys derived by incrementing the last ID in the path of 823 `complete_key` sequentially to obtain the number of keys specified in 824 `num_ids`. 825 826 :type complete_key: :class:`google.cloud.datastore.key.Key` 827 :param complete_key: 828 Complete key to use as base for reserved IDs. Key must use a 829 numeric ID and not a string name. 830 831 :type num_ids: int 832 :param num_ids: The number of IDs to reserve. 833 834 :type retry: :class:`google.api_core.retry.Retry` 835 :param retry: 836 A retry object used to retry requests. If ``None`` is specified, 837 requests will be retried using a default configuration. 838 839 :type timeout: float 840 :param timeout: 841 Time, in seconds, to wait for the request to complete. 842 Note that if ``retry`` is specified, the timeout applies 843 to each individual attempt. 844 845 :rtype: class:`NoneType` 846 :returns: None 847 :raises: :class:`ValueError` if `complete_key`` is not a 848 Complete key. 849 """ 850 if complete_key.is_partial: 851 raise ValueError(("Key is not Complete.", complete_key)) 852 853 if complete_key.id is None: 854 raise ValueError(("Key must use numeric id.", complete_key)) 855 856 if not isinstance(num_ids, int): 857 raise ValueError(("num_ids is not a valid integer.", num_ids)) 858 859 key_class = type(complete_key) 860 namespace = complete_key._namespace 861 project = complete_key._project 862 flat_path = list(complete_key._flat_path[:-1]) 863 start_id = complete_key._flat_path[-1] 864 865 key_pbs = [] 866 for id in range(start_id, start_id + num_ids): 867 path = flat_path + [id] 868 key = key_class(*path, project=project, namespace=namespace) 869 key_pbs.append(key.to_protobuf()) 870 871 kwargs = _make_retry_timeout_kwargs(retry, timeout) 872 self._datastore_api.reserve_ids( 873 request={"project_id": complete_key.project, "keys": key_pbs}, **kwargs 874 ) 875 return None 876 877 def reserve_ids(self, complete_key, num_ids, retry=None, timeout=None): 878 """Reserve a list of IDs sequentially from a complete key. 879 880 DEPRECATED. Alias for :meth:`reserve_ids_sequential`. 881 882 Please use either :meth:`reserve_ids_multi` (recommended) or 883 :meth:`reserve_ids_sequential`. 884 """ 885 warnings.warn(_RESERVE_IDS_DEPRECATED_MESSAGE, DeprecationWarning) 886 return self.reserve_ids_sequential( 887 complete_key, num_ids, retry=retry, timeout=timeout 888 ) 889 890 def reserve_ids_multi(self, complete_keys, retry=None, timeout=None): 891 """Reserve IDs from a list of complete keys. 892 893 :type complete_keys: `list` of :class:`google.cloud.datastore.key.Key` 894 :param complete_keys: 895 Complete keys for which to reserve IDs. 896 897 :type retry: :class:`google.api_core.retry.Retry` 898 :param retry: 899 A retry object used to retry requests. If ``None`` is specified, 900 requests will be retried using a default configuration. 901 902 :type timeout: float 903 :param timeout: 904 Time, in seconds, to wait for the request to complete. 905 Note that if ``retry`` is specified, the timeout applies 906 to each individual attempt. 907 908 :rtype: class:`NoneType` 909 :returns: None 910 :raises: :class:`ValueError` if any of `complete_keys`` is not a 911 Complete key. 912 """ 913 for complete_key in complete_keys: 914 if complete_key.is_partial: 915 raise ValueError(("Key is not Complete.", complete_key)) 916 917 kwargs = _make_retry_timeout_kwargs(retry, timeout) 918 key_pbs = [key.to_protobuf() for key in complete_keys] 919 self._datastore_api.reserve_ids( 920 request={"project_id": complete_keys[0].project, "keys": key_pbs}, **kwargs 921 ) 922 923 return None 924