def _collection_group_query_response_to_snapshot( response_pb: RunQueryResponse, collection ) -> Optional[document.DocumentSnapshot]: """Parse a query response protobuf to a document snapshot. Args: response_pb (google.cloud.proto.firestore.v1.\ firestore.RunQueryResponse): A collection (:class:`~google.cloud.firestore_v1.collection.CollectionReference`): A reference to the collection that initiated the query. Returns: Optional[:class:`~google.cloud.firestore.document.DocumentSnapshot`]: A snapshot of the data returned in the query. If ``response_pb.document`` is not set, the snapshot will be :data:`None`. """ if not response_pb._pb.HasField("document"): return None reference = collection._client.document(response_pb.document.name) data = _helpers.decode_dict(response_pb.document.fields, collection._client) snapshot = document.DocumentSnapshot( reference, data, exists=True, read_time=response_pb._pb.read_time, create_time=response_pb._pb.document.create_time, update_time=response_pb._pb.document.update_time, ) return snapshot
def _query_response_to_snapshot( response_pb: RunQueryResponse, collection, expected_prefix: str ) -> Optional[document.DocumentSnapshot]: """Parse a query response protobuf to a document snapshot. Args: response_pb (google.cloud.proto.firestore.v1.\ firestore.RunQueryResponse): A collection (:class:`~google.cloud.firestore_v1.collection.CollectionReference`): A reference to the collection that initiated the query. expected_prefix (str): The expected prefix for fully-qualified document names returned in the query results. This can be computed directly from ``collection`` via :meth:`_parent_info`. Returns: Optional[:class:`~google.cloud.firestore.document.DocumentSnapshot`]: A snapshot of the data returned in the query. If ``response_pb.document`` is not set, the snapshot will be :data:`None`. """ if not response_pb._pb.HasField("document"): return None document_id = _helpers.get_doc_id(response_pb.document, expected_prefix) reference = collection.document(document_id) data = _helpers.decode_dict(response_pb.document.fields, collection._client) snapshot = document.DocumentSnapshot( reference, data, exists=True, read_time=response_pb.read_time, create_time=response_pb.document.create_time, update_time=response_pb.document.update_time, ) return snapshot
def _query_response_to_snapshot(response_pb, collection, expected_prefix): """Parse a query response protobuf to a document snapshot. Args: response_pb (google.cloud.proto.firestore.v1.\ firestore_pb2.RunQueryResponse): A collection (:class:`~google.cloud.firestore_v1.collection.CollectionReference`): A reference to the collection that initiated the query. expected_prefix (str): The expected prefix for fully-qualified document names returned in the query results. This can be computed directly from ``collection`` via :meth:`_parent_info`. Returns: Optional[:class:`~google.cloud.firestore.document.DocumentSnapshot`]: A snapshot of the data returned in the query. If ``response_pb.document`` is not set, the snapshot will be :data:`None`. """ if not response_pb.HasField("document"): return None document_id = _helpers.get_doc_id(response_pb.document, expected_prefix) reference = collection.document(document_id) data = _helpers.decode_dict(response_pb.document.fields, collection._client) snapshot = document.DocumentSnapshot( reference, data, exists=True, read_time=response_pb.read_time, create_time=response_pb.document.create_time, update_time=response_pb.document.update_time, ) return snapshot
def _collection_group_query_response_to_snapshot(response_pb, collection): """Parse a query response protobuf to a document snapshot. Args: response_pb (google.cloud.proto.firestore.v1.\ firestore_pb2.RunQueryResponse): A collection (:class:`~google.cloud.firestore_v1.collection.CollectionReference`): A reference to the collection that initiated the query. Returns: Optional[:class:`~google.cloud.firestore.document.DocumentSnapshot`]: A snapshot of the data returned in the query. If ``response_pb.document`` is not set, the snapshot will be :data:`None`. """ if not response_pb.HasField("document"): return None reference = collection._client.document(response_pb.document.name) data = _helpers.decode_dict(response_pb.document.fields, collection._client) snapshot = document.DocumentSnapshot( reference, data, exists=True, read_time=response_pb.read_time, create_time=response_pb.document.create_time, update_time=response_pb.document.update_time, ) return snapshot
def _parse_batch_get( get_doc_response: types.BatchGetDocumentsResponse, reference_map: dict, client: BaseClient, ) -> DocumentSnapshot: """Parse a `BatchGetDocumentsResponse` protobuf. Args: get_doc_response (~google.cloud.proto.firestore.v1.\ firestore.BatchGetDocumentsResponse): A single response (from a stream) containing the "get" response for a document. reference_map (Dict[str, .DocumentReference]): A mapping (produced by :func:`_reference_info`) of fully-qualified document paths to document references. client (:class:`~google.cloud.firestore_v1.client.Client`): A client that has a document factory. Returns: [.DocumentSnapshot]: The retrieved snapshot. Raises: ValueError: If the response has a ``result`` field (a oneof) other than ``found`` or ``missing``. """ result_type = get_doc_response._pb.WhichOneof("result") if result_type == "found": reference = _get_reference(get_doc_response.found.name, reference_map) data = _helpers.decode_dict(get_doc_response.found.fields, client) snapshot = DocumentSnapshot( reference, data, exists=True, read_time=get_doc_response.read_time, create_time=get_doc_response.found.create_time, update_time=get_doc_response.found.update_time, ) elif result_type == "missing": reference = _get_reference(get_doc_response.missing, reference_map) snapshot = DocumentSnapshot( reference, None, exists=False, read_time=get_doc_response.read_time, create_time=None, update_time=None, ) else: raise ValueError( "`BatchGetDocumentsResponse.result` (a oneof) had a field other " "than `found` or `missing` set, or was unset" ) return snapshot
def _add_bundle_element(self, bundle_element: BundleElement, *, client: BaseClient, type: str): # type: ignore """Applies BundleElements to this FirestoreBundle instance as a part of deserializing a FirestoreBundle string. """ from google.cloud.firestore_v1.types.document import Document if getattr(self, "_doc_metadata_map", None) is None: self._doc_metadata_map = {} if type == "metadata": self._deserialized_metadata = bundle_element.metadata # type: ignore elif type == "namedQuery": self.named_queries[ bundle_element.named_query. name] = bundle_element.named_query # type: ignore elif type == "documentMetadata": self._doc_metadata_map[bundle_element.document_metadata. name] = bundle_element.document_metadata elif type == "document": doc_ref_value = _helpers.DocumentReferenceValue( bundle_element.document.name) snapshot = DocumentSnapshot( data=_helpers.decode_dict( Document(mapping=bundle_element.document).fields, client), exists=True, reference=DocumentReference( doc_ref_value.collection_name, doc_ref_value.document_id, client=client, ), read_time=self._doc_metadata_map[ bundle_element.document.name].read_time, create_time=bundle_element.document. create_time, # type: ignore update_time=bundle_element.document. update_time, # type: ignore ) self.add_document(snapshot) bundled_document = self.documents.get( snapshot.reference._document_path) for query_name in self._doc_metadata_map[ bundle_element.document.name].queries: bundled_document.metadata.queries.append( query_name) # type: ignore else: raise ValueError(f"Unexpected type of BundleElement: {type}")
def _parse_batch_get(get_doc_response, reference_map, client): """Parse a `BatchGetDocumentsResponse` protobuf. Args: get_doc_response (~google.cloud.proto.firestore.v1.\ firestore_pb2.BatchGetDocumentsResponse): A single response (from a stream) containing the "get" response for a document. reference_map (Dict[str, .DocumentReference]): A mapping (produced by :func:`_reference_info`) of fully-qualified document paths to document references. client (~.firestore_v1.client.Client): A client that has a document factory. Returns: [.DocumentSnapshot]: The retrieved snapshot. Raises: ValueError: If the response has a ``result`` field (a oneof) other than ``found`` or ``missing``. """ result_type = get_doc_response.WhichOneof("result") if result_type == "found": reference = _get_reference(get_doc_response.found.name, reference_map) data = _helpers.decode_dict(get_doc_response.found.fields, client) snapshot = DocumentSnapshot( reference, data, exists=True, read_time=get_doc_response.read_time, create_time=get_doc_response.found.create_time, update_time=get_doc_response.found.update_time, ) elif result_type == "missing": snapshot = DocumentSnapshot( None, None, exists=False, read_time=get_doc_response.read_time, create_time=None, update_time=None, ) else: raise ValueError( "`BatchGetDocumentsResponse.result` (a oneof) had a field other " "than `found` or `missing` set, or was unset" ) return snapshot
def get(self, field_paths=None, transaction=None): """Retrieve a snapshot of the current document. See :meth:`~google.cloud.firestore_v1.client.Client.field_path` for more information on **field paths**. If a ``transaction`` is used and it already has write operations added, this method cannot be used (i.e. read-after-write is not allowed). Args: field_paths (Optional[Iterable[str, ...]]): An iterable of field paths (``.``-delimited list of field names) to use as a projection of document fields in the returned results. If no value is provided, all fields will be returned. transaction (Optional[:class:`~google.cloud.firestore_v1.transaction.Transaction`]): An existing transaction that this reference will be retrieved in. Returns: :class:`~google.cloud.firestore_v1.document.DocumentSnapshot`: A snapshot of the current document. If the document does not exist at the time of the snapshot is taken, the snapshot's :attr:`reference`, :attr:`data`, :attr:`update_time`, and :attr:`create_time` attributes will all be ``None`` and its :attr:`exists` attribute will be ``False``. """ if isinstance(field_paths, six.string_types): raise ValueError("'field_paths' must be a sequence of paths, not a string.") if field_paths is not None: mask = common_pb2.DocumentMask(field_paths=sorted(field_paths)) else: mask = None firestore_api = self._client._firestore_api try: document_pb = firestore_api.get_document( self._document_path, mask=mask, transaction=_helpers.get_transaction_id(transaction), metadata=self._client._rpc_metadata, ) except exceptions.NotFound: data = None exists = False create_time = None update_time = None else: data = _helpers.decode_dict(document_pb.fields, self._client) exists = True create_time = document_pb.create_time update_time = document_pb.update_time return DocumentSnapshot( reference=self, data=data, exists=exists, read_time=None, # No server read_time available create_time=create_time, update_time=update_time, )
def on_snapshot(self, proto): """ Called everytime there is a response from listen. Collect changes and 'push' the changes in a batch to the customer when we receive 'current' from the listen response. Args: listen_response(`google.cloud.firestore_v1.types.ListenResponse`): Callback method that receives a object to """ TargetChange = firestore_pb2.TargetChange target_changetype_dispatch = { TargetChange.NO_CHANGE: self._on_snapshot_target_change_no_change, TargetChange.ADD: self._on_snapshot_target_change_add, TargetChange.REMOVE: self._on_snapshot_target_change_remove, TargetChange.RESET: self._on_snapshot_target_change_reset, TargetChange.CURRENT: self._on_snapshot_target_change_current, } target_change = getattr(proto, "target_change", "") document_change = getattr(proto, "document_change", "") document_delete = getattr(proto, "document_delete", "") document_remove = getattr(proto, "document_remove", "") filter_ = getattr(proto, "filter", "") if str(target_change): target_change_type = target_change.target_change_type _LOGGER.debug("on_snapshot: target change: " + str(target_change_type)) meth = target_changetype_dispatch.get(target_change_type) if meth is None: _LOGGER.info("on_snapshot: Unknown target change " + str(target_change_type)) self.close(reason="Unknown target change type: %s " % str(target_change_type)) else: try: meth(proto) except Exception as exc2: _LOGGER.debug("meth(proto) exc: " + str(exc2)) raise # NOTE: # in other implementations, such as node, the backoff is reset here # in this version bidi rpc is just used and will control this. elif str(document_change): _LOGGER.debug("on_snapshot: document change") # No other target_ids can show up here, but we still need to see # if the targetId was in the added list or removed list. target_ids = document_change.target_ids or [] removed_target_ids = document_change.removed_target_ids or [] changed = False removed = False if WATCH_TARGET_ID in target_ids: changed = True if WATCH_TARGET_ID in removed_target_ids: removed = True if changed: _LOGGER.debug("on_snapshot: document change: CHANGED") # google.cloud.firestore_v1.types.Document document = document_change.document data = _helpers.decode_dict(document.fields, self._firestore) # Create a snapshot. As Document and Query objects can be # passed we need to get a Document Reference in a more manual # fashion than self._document_reference document_name = document.name db_str = self._firestore._database_string db_str_documents = db_str + "/documents/" if document_name.startswith(db_str_documents): document_name = document_name[len(db_str_documents):] document_ref = self._firestore.document(document_name) snapshot = self.DocumentSnapshot( reference=document_ref, data=data, exists=True, read_time=None, create_time=document.create_time, update_time=document.update_time, ) self.change_map[document.name] = snapshot elif removed: _LOGGER.debug("on_snapshot: document change: REMOVED") document = document_change.document self.change_map[document.name] = ChangeType.REMOVED # NB: document_delete and document_remove (as far as we, the client, # are concerned) are functionally equivalent elif str(document_delete): _LOGGER.debug("on_snapshot: document change: DELETE") name = document_delete.document self.change_map[name] = ChangeType.REMOVED elif str(document_remove): _LOGGER.debug("on_snapshot: document change: REMOVE") name = document_remove.document self.change_map[name] = ChangeType.REMOVED elif filter_: _LOGGER.debug("on_snapshot: filter update") if filter_.count != self._current_size(): # We need to remove all the current results. self._reset_docs() # The filter didn't match, so re-issue the query. # TODO: reset stream method? # self._reset_stream(); elif proto is None: self.close() else: _LOGGER.debug("UNKNOWN TYPE. UHOH") self.close(reason=ValueError("Unknown listen response type: %s" % proto))
def on_snapshot(self, proto): """ Called everytime there is a response from listen. Collect changes and 'push' the changes in a batch to the customer when we receive 'current' from the listen response. Args: listen_response(`google.cloud.firestore_v1.types.ListenResponse`): Callback method that receives a object to """ TargetChange = firestore_pb2.TargetChange target_changetype_dispatch = { TargetChange.NO_CHANGE: self._on_snapshot_target_change_no_change, TargetChange.ADD: self._on_snapshot_target_change_add, TargetChange.REMOVE: self._on_snapshot_target_change_remove, TargetChange.RESET: self._on_snapshot_target_change_reset, TargetChange.CURRENT: self._on_snapshot_target_change_current, } target_change = proto.target_change if str(target_change): target_change_type = target_change.target_change_type _LOGGER.debug("on_snapshot: target change: " + str(target_change_type)) meth = target_changetype_dispatch.get(target_change_type) if meth is None: _LOGGER.info( "on_snapshot: Unknown target change " + str(target_change_type) ) self.close( reason="Unknown target change type: %s " % str(target_change_type) ) else: try: meth(proto) except Exception as exc2: _LOGGER.debug("meth(proto) exc: " + str(exc2)) raise # NOTE: # in other implementations, such as node, the backoff is reset here # in this version bidi rpc is just used and will control this. elif str(proto.document_change): _LOGGER.debug("on_snapshot: document change") # No other target_ids can show up here, but we still need to see # if the targetId was in the added list or removed list. target_ids = proto.document_change.target_ids or [] removed_target_ids = proto.document_change.removed_target_ids or [] changed = False removed = False if WATCH_TARGET_ID in target_ids: changed = True if WATCH_TARGET_ID in removed_target_ids: removed = True if changed: _LOGGER.debug("on_snapshot: document change: CHANGED") # google.cloud.firestore_v1.types.DocumentChange document_change = proto.document_change # google.cloud.firestore_v1.types.Document document = document_change.document data = _helpers.decode_dict(document.fields, self._firestore) # Create a snapshot. As Document and Query objects can be # passed we need to get a Document Reference in a more manual # fashion than self._document_reference document_name = document.name db_str = self._firestore._database_string db_str_documents = db_str + "/documents/" if document_name.startswith(db_str_documents): document_name = document_name[len(db_str_documents) :] document_ref = self._firestore.document(document_name) snapshot = self.DocumentSnapshot( reference=document_ref, data=data, exists=True, read_time=None, create_time=document.create_time, update_time=document.update_time, ) self.change_map[document.name] = snapshot elif removed: _LOGGER.debug("on_snapshot: document change: REMOVED") document = proto.document_change.document self.change_map[document.name] = ChangeType.REMOVED # NB: document_delete and document_remove (as far as we, the client, # are concerned) are functionally equivalent elif str(proto.document_delete): _LOGGER.debug("on_snapshot: document change: DELETE") name = proto.document_delete.document self.change_map[name] = ChangeType.REMOVED elif str(proto.document_remove): _LOGGER.debug("on_snapshot: document change: REMOVE") name = proto.document_remove.document self.change_map[name] = ChangeType.REMOVED elif proto.filter: _LOGGER.debug("on_snapshot: filter update") if proto.filter.count != self._current_size(): # We need to remove all the current results. self._reset_docs() # The filter didn't match, so re-issue the query. # TODO: reset stream method? # self._reset_stream(); else: _LOGGER.debug("UNKNOWN TYPE. UHOH") self.close(reason=ValueError("Unknown listen response type: %s" % proto))
def get(self, field_paths=None, transaction=None) -> DocumentSnapshot: """Retrieve a snapshot of the current document. See :meth:`~google.cloud.firestore_v1.base_client.BaseClient.field_path` for more information on **field paths**. If a ``transaction`` is used and it already has write operations added, this method cannot be used (i.e. read-after-write is not allowed). Args: field_paths (Optional[Iterable[str, ...]]): An iterable of field paths (``.``-delimited list of field names) to use as a projection of document fields in the returned results. If no value is provided, all fields will be returned. transaction (Optional[:class:`~google.cloud.firestore_v1.transaction.Transaction`]): An existing transaction that this reference will be retrieved in. Returns: :class:`~google.cloud.firestore_v1.base_document.DocumentSnapshot`: A snapshot of the current document. If the document does not exist at the time of the snapshot is taken, the snapshot's :attr:`reference`, :attr:`data`, :attr:`update_time`, and :attr:`create_time` attributes will all be ``None`` and its :attr:`exists` attribute will be ``False``. """ if isinstance(field_paths, str): raise ValueError("'field_paths' must be a sequence of paths, not a string.") if field_paths is not None: mask = common.DocumentMask(field_paths=sorted(field_paths)) else: mask = None firestore_api = self._client._firestore_api try: document_pb = firestore_api.get_document( request={ "name": self._document_path, "mask": mask, "transaction": _helpers.get_transaction_id(transaction), }, metadata=self._client._rpc_metadata, ) except exceptions.NotFound: data = None exists = False create_time = None update_time = None else: data = _helpers.decode_dict(document_pb.fields, self._client) exists = True create_time = document_pb.create_time update_time = document_pb.update_time return DocumentSnapshot( reference=self, data=data, exists=exists, read_time=None, # No server read_time available create_time=create_time, update_time=update_time, )
def get( self, field_paths: Iterable[str] = None, transaction=None, retry: retries.Retry = gapic_v1.method.DEFAULT, timeout: float = None, ) -> DocumentSnapshot: """Retrieve a snapshot of the current document. See :meth:`~google.cloud.firestore_v1.base_client.BaseClient.field_path` for more information on **field paths**. If a ``transaction`` is used and it already has write operations added, this method cannot be used (i.e. read-after-write is not allowed). Args: field_paths (Optional[Iterable[str, ...]]): An iterable of field paths (``.``-delimited list of field names) to use as a projection of document fields in the returned results. If no value is provided, all fields will be returned. transaction (Optional[:class:`~google.cloud.firestore_v1.transaction.Transaction`]): An existing transaction that this reference will be retrieved in. retry (google.api_core.retry.Retry): Designation of what errors, if an y, should be retried. Defaults to a system-specified policy. timeout (float): The timeout for this request. Defaults to a system-specified value. Returns: :class:`~google.cloud.firestore_v1.base_document.DocumentSnapshot`: A snapshot of the current document. If the document does not exist at the time of the snapshot is taken, the snapshot's :attr:`reference`, :attr:`data`, :attr:`update_time`, and :attr:`create_time` attributes will all be ``None`` and its :attr:`exists` attribute will be ``False``. """ request, kwargs = self._prep_get(field_paths, transaction, retry, timeout) firestore_api = self._client._firestore_api try: document_pb = firestore_api.get_document( request=request, metadata=self._client._rpc_metadata, **kwargs, ) except exceptions.NotFound: data = None exists = False create_time = None update_time = None else: data = _helpers.decode_dict(document_pb.fields, self._client) exists = True create_time = document_pb.create_time update_time = document_pb.update_time return DocumentSnapshot( reference=self, data=data, exists=exists, read_time=None, # No server read_time available create_time=create_time, update_time=update_time, )
def on_snapshot(self, proto): """Process a response from the bi-directional gRPC stream. Collect changes and push the changes in a batch to the customer when we receive 'current' from the listen response. Args: proto(`google.cloud.firestore_v1.types.ListenResponse`): Callback method that receives a object to """ if proto is None: self.close() return pb = proto._pb which = pb.WhichOneof("response_type") if which == "target_change": target_change_type = pb.target_change.target_change_type _LOGGER.debug(f"on_snapshot: target change: {target_change_type}") meth = self._target_changetype_dispatch.get(target_change_type) if meth is None: message = f"Unknown target change type: {target_change_type}" _LOGGER.info(f"on_snapshot: {message}") self.close(reason=ValueError(message)) try: # Use 'proto' vs 'pb' for datetime handling meth(self, proto.target_change) except Exception as exc2: _LOGGER.debug(f"meth(proto) exc: {exc2}") raise # NOTE: # in other implementations, such as node, the backoff is reset here # in this version bidi rpc is just used and will control this. elif which == "document_change": _LOGGER.debug("on_snapshot: document change") # No other target_ids can show up here, but we still need to see # if the targetId was in the added list or removed list. changed = WATCH_TARGET_ID in pb.document_change.target_ids removed = WATCH_TARGET_ID in pb.document_change.removed_target_ids # google.cloud.firestore_v1.types.Document # Use 'proto' vs 'pb' for datetime handling document = proto.document_change.document if changed: _LOGGER.debug("on_snapshot: document change: CHANGED") data = _helpers.decode_dict(document.fields, self._firestore) # Create a snapshot. As Document and Query objects can be # passed we need to get a Document Reference in a more manual # fashion than self._document_reference document_name = self._strip_document_pfx(document.name) document_ref = self._firestore.document(document_name) snapshot = self._document_snapshot_cls( reference=document_ref, data=data, exists=True, read_time=None, create_time=document.create_time, update_time=document.update_time, ) self.change_map[document.name] = snapshot elif removed: _LOGGER.debug("on_snapshot: document change: REMOVED") self.change_map[document.name] = ChangeType.REMOVED # NB: document_delete and document_remove (as far as we, the client, # are concerned) are functionally equivalent elif which == "document_delete": _LOGGER.debug("on_snapshot: document change: DELETE") name = pb.document_delete.document self.change_map[name] = ChangeType.REMOVED elif which == "document_remove": _LOGGER.debug("on_snapshot: document change: REMOVE") name = pb.document_remove.document self.change_map[name] = ChangeType.REMOVED elif which == "filter": _LOGGER.debug("on_snapshot: filter update") if pb.filter.count != self._current_size(): # First, shut down current stream _LOGGER.info("Filter mismatch -- restarting stream.") thread = threading.Thread( name=_RPC_ERROR_THREAD_NAME, target=self.close, ) thread.start() thread.join() # wait for shutdown to complete # Then, remove all the current results. self._reset_docs() # Finally, restart stream. self._init_stream() else: _LOGGER.debug("UNKNOWN TYPE. UHOH") message = f"Unknown listen response type: {proto}" self.close(reason=ValueError(message))