Beispiel #1
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
Beispiel #2
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}")
    async def get(
        self,
        field_paths: Iterable[str] = None,
        transaction=None,
        retry: retries.Retry = gapic_v1.method.DEFAULT,
        timeout: float = None,
    ) -> Union[DocumentSnapshot, Coroutine[Any, Any, 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.async_transaction.AsyncTransaction`]):
                An existing transaction that this reference
                will be retrieved in.
            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                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``.
        """
        from google.cloud.firestore_v1.base_client import _parse_batch_get

        request, kwargs = self._prep_batch_get(field_paths, transaction, retry,
                                               timeout)

        response_iter = await self._client._firestore_api.batch_get_documents(
            request=request,
            metadata=self._client._rpc_metadata,
            **kwargs,
        )

        async for resp in response_iter:
            # Immediate return as the iterator should only ever have one item.
            return _parse_batch_get(
                get_doc_response=resp,
                reference_map={self._document_path: self},
                client=self._client,
            )

        logger.warning(
            "`batch_get_documents` unexpectedly returned empty "
            "stream. Expected one object.", )

        return DocumentSnapshot(
            self,
            None,
            exists=False,
            read_time=_datetime_to_pb_timestamp(datetime.datetime.now()),
            create_time=None,
            update_time=None,
        )
Beispiel #4
0
def _fmpfeedback_mailgun_send(fs_feedback_doc: DocumentSnapshot) -> bool:
    """Forward feedback via email message through Mailgun ESP REST API

    Args:
        fs_feedback_doc (DocumentSnapshot): Feedback document

    Returns:
        bool: True if email is accepted for delivery.
    """

    feedback_doc = fs_feedback_doc.to_dict()

    def _abort_return(internal_error: str = None) -> bool:
        if internal_error:
            print(f"ERROR! Forward feedback failed: {internal_error}")
        return False

    if feedback_doc[FEEDBACKDOC_FIELD_ARCHIVEDTIMESTAMP]:
        print(
            f"Ignoring feedback document {fs_feedback_doc.id}: Feedback has already been archived"
        )
        return True

    for field in (FEEDBACKDOC_FIELD_EMAIL, FEEDBACKDOC_FIELD_SUBJECT,
                  FEEDBACKDOC_FIELD_MESSAGE):
        if not feedback_doc[field]:
            print(
                f"Ignoring feedback document {fs_feedback_doc.id}: Field '{field}' value is not set"
            )

    attachments = []

    if feedback_doc[FEEDBACKDOC_FIELD_HASUPLOADS]:
        try:
            fs_upload_docs = fs_feedback_doc.reference.collection(
                FEEDBACK_UPLOADS_SUBCOLLECTION).get()
        except google.auth.exceptions.GoogleAuthError as e:  # GoogleAuthError(Exception)
            return _abort_return(f"Firestore auth exception: {e}")
        except google.api_core.exceptions.ClientError as e:  # ClientError(GoogleAPICallError)
            return _abort_return(f"Firestore client exception: {e}")
        except google.api_core.exceptions.GoogleAPIError as e:  # GoogleAPIError(Exception)
            return _abort_return(f"Firestore API exception: {e}")
        except Exception as e:
            return _abort_return(f"Unexpected exception: {e}")
        else:
            for fs_upload_doc in fs_upload_docs:
                upload_doc = fs_upload_doc.to_dict()
                filename = upload_doc[UPLOADDOC_FIELD_FILENAME]
                data = upload_doc[UPLOADDOC_FIELD_DATA]
                mime_type, _ = mimetypes.guess_type(filename, strict=False)
                attachments.append(("attachment", (filename, data, mime_type
                                                   or "")))

    from_name = feedback_doc[FEEDBACKDOC_FIELD_NAME] or False
    from_email = feedback_doc[FEEDBACKDOC_FIELD_EMAIL]
    reply_to = email.utils.formataddr((from_name, from_email))
    sender = email.utils.formataddr(
        (f"{reply_to} via", MAILGUN_SENDER_ADDR
         ))  # set "realname" to requester for clearer MUA presentation

    message_data = {
        "from": sender,
        "to": MAILGUN_RECIPIENT,
        "subject": feedback_doc[FEEDBACKDOC_FIELD_SUBJECT],
        "text": feedback_doc[FEEDBACKDOC_FIELD_MESSAGE],
        "h:sender": sender,  # prevent some MUA from showing "on behalf of"
        'h:reply-to': reply_to,  # allow "reply all" to include requester
        'h:X-Origin-Mailer': "FMPFeedbackGCPService.fmpfeedback_mailgun",
    }

    print(
        f"Forwarding feedback {fs_feedback_doc.id}: from '{reply_to}' to '{MAILGUN_SENDER_ADDR}' with {len(attachments)} attachments"
    )

    try:
        response = requests.post(MAILGUN_REQUESTS_URL,
                                 auth=(MAILGUN_API_AUTHUSER, MAILGUN_API_KEY),
                                 files=attachments,
                                 data=message_data)

        response.raise_for_status()

    except requests.exceptions.HTTPError as e:
        return _abort_return(f"Mailgun API HTTP exception: {e}")
    except requests.exceptions.RequestException as e:
        return _abort_return(f"Mailgun API request exception: {e}")
    except Exception as e:
        return _abort_return(f"Mailgun API unexpected exception: {e}")
    else:
        response_json = response.json()
        message_id = response_json["id"][1:-1]  # "<id>"

        print(f"Mailgun message accepted: message-id {message_id}")

        feedback_doc_updates = {
            FEEDBACKDOC_FIELD_ARCHIVEDTIMESTAMP:
            datetime.now(timezone.utc).isoformat(timespec="seconds"),
            FEEDBACKDOC_FIELD_MAILGUN_MESSAGEID:
            message_id,
        }

        # Tag message as being dealt with
        fs_feedback_doc.reference.update(feedback_doc_updates)

    return True
Beispiel #5
0
 def from_firebase(cls, obj: DocumentSnapshot):
     _ = obj.to_dict()
     _.update({"uid": obj.id})
     return cls(**_)
Beispiel #6
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,
        )
    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,
        )