コード例 #1
0
ファイル: base_query.py プロジェクト: yvdjee/python-firestore
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
コード例 #2
0
ファイル: base_query.py プロジェクト: yvdjee/python-firestore
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
コード例 #3
0
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
コード例 #4
0
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
コード例 #5
0
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
コード例 #6
0
    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}")
コード例 #7
0
ファイル: client.py プロジェクト: tswast/gcloud-python
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
コード例 #8
0
    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,
        )
コード例 #9
0
    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))
コード例 #10
0
ファイル: watch.py プロジェクト: tswast/gcloud-python
    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))
コード例 #11
0
    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,
        )
コード例 #12
0
    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,
        )
コード例 #13
0
ファイル: watch.py プロジェクト: kolea2/python-firestore
    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))