Source code for azure.communication.chat._chat_thread_client

# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
from typing import TYPE_CHECKING

try:
    from urllib.parse import urlparse
except ImportError:
    from urlparse import urlparse # type: ignore

from azure.core.tracing.decorator import distributed_trace
from azure.core.pipeline.policies import BearerTokenCredentialPolicy

from ._shared.user_credential import CommunicationTokenCredential
from ._generated import AzureCommunicationChatService
from ._generated.models import (
    AddChatParticipantsRequest,
    SendReadReceiptRequest,
    SendChatMessageRequest,
    UpdateChatMessageRequest,
    UpdateChatThreadRequest,
    ChatMessageType
)
from ._models import (
    ChatThreadParticipant,
    ChatMessage,
    ChatMessageReadReceipt
)

from ._utils import ( # pylint: disable=unused-import
    _to_utc_datetime,
    CommunicationUserIdentifierConverter,
    CommunicationErrorResponseConverter
)
from ._version import SDK_MONIKER

if TYPE_CHECKING:
    # pylint: disable=unused-import,ungrouped-imports
    from typing import Any, Callable, Dict, Generic, List, Optional, TypeVar, Union
    from datetime import datetime
    from azure.core.paging import ItemPaged


[docs]class ChatThreadClient(object): """A client to interact with the AzureCommunicationService Chat gateway. Instances of this class is normally created by ChatClient.create_chat_thread() This client provides operations to add participant to chat thread, remove participant from chat thread, send message, delete message, update message, send typing notifications, send and list read receipt :ivar thread_id: Chat thread id. :vartype thread_id: str :param str endpoint: The endpoint of the Azure Communication resource. :param CommunicationTokenCredential credential: The credentials with which to authenticate. The value contains a User Access Token :param str thread_id: The unique thread id. .. admonition:: Example: .. literalinclude:: ../samples/chat_thread_client_sample.py :start-after: [START create_chat_thread_client] :end-before: [END create_chat_thread_client] :language: python :dedent: 8 :caption: Creating the ChatThreadClient. """ def __init__( self, endpoint, # type: str credential, # type: CommunicationTokenCredential thread_id, # type: str **kwargs # type: Any ): # type: (...) -> None if not thread_id: raise ValueError("thread_id can not be None or empty") if not credential: raise ValueError("credential can not be None") try: if not endpoint.lower().startswith('http'): endpoint = "https://" + endpoint except AttributeError: raise ValueError("Host URL must be a string") parsed_url = urlparse(endpoint.rstrip('/')) if not parsed_url.netloc: raise ValueError("Invalid URL: {}".format(endpoint)) self._thread_id = thread_id self._endpoint = endpoint self._credential = credential self._client = AzureCommunicationChatService( endpoint, authentication_policy=BearerTokenCredentialPolicy(self._credential), sdk_moniker=SDK_MONIKER, **kwargs ) @property def thread_id(self): # type: () -> str """ Gets the thread id from the client. :rtype: str """ return self._thread_id
[docs] @distributed_trace def update_topic( self, topic=None, # type: Optional[str] **kwargs # type: Any ): # type: (...) -> None """Updates a thread's properties. :param topic: Thread topic. If topic is not specified, the update will succeeded but chat thread properties will not be changed. :type topic: str :return: None :rtype: None :raises: ~azure.core.exceptions.HttpResponseError, ValueError .. admonition:: Example: .. literalinclude:: ../samples/chat_thread_client_sample.py :start-after: [START update_topic] :end-before: [END update_topic] :language: python :dedent: 8 :caption: Updating chat thread. """ update_topic_request = UpdateChatThreadRequest(topic=topic) return self._client.chat_thread.update_chat_thread( chat_thread_id=self._thread_id, update_chat_thread_request=update_topic_request, **kwargs)
[docs] @distributed_trace def send_read_receipt( self, message_id, # type: str **kwargs # type: Any ): # type: (...) -> None """Posts a read receipt event to a thread, on behalf of a user. :param message_id: Required. Id of the latest message read by current user. :type message_id: str :return: None :rtype: None :raises: ~azure.core.exceptions.HttpResponseError, ValueError .. admonition:: Example: .. literalinclude:: ../samples/chat_thread_client_sample.py :start-after: [START send_read_receipt] :end-before: [END send_read_receipt] :language: python :dedent: 8 :caption: Sending read receipt of a chat message. """ if not message_id: raise ValueError("message_id cannot be None.") post_read_receipt_request = SendReadReceiptRequest(chat_message_id=message_id) return self._client.chat_thread.send_chat_read_receipt( self._thread_id, send_read_receipt_request=post_read_receipt_request, **kwargs)
[docs] @distributed_trace def list_read_receipts( self, **kwargs # type: Any ): # type: (...) -> ItemPaged[ChatMessageReadReceipt] """Gets read receipts for a thread. :keyword int results_per_page: The maximum number of chat message read receipts to be returned per page. :keyword int skip: Skips chat message read receipts up to a specified position in response. :return: An iterator like instance of ChatMessageReadReceipt :rtype: ~azure.core.paging.ItemPaged[~azure.communication.chat.ChatMessageReadReceipt] :raises: ~azure.core.exceptions.HttpResponseError, ValueError .. admonition:: Example: .. literalinclude:: ../samples/chat_thread_client_sample.py :start-after: [START list_read_receipts] :end-before: [END list_read_receipts] :language: python :dedent: 8 :caption: Listing read receipts. """ results_per_page = kwargs.pop("results_per_page", None) skip = kwargs.pop("skip", None) return self._client.chat_thread.list_chat_read_receipts( self._thread_id, max_page_size=results_per_page, skip=skip, cls=lambda objs: [ChatMessageReadReceipt._from_generated(x) for x in objs], # pylint:disable=protected-access **kwargs)
[docs] @distributed_trace def send_typing_notification( self, **kwargs # type: Any ): # type: (...) -> None """Posts a typing event to a thread, on behalf of a user. :return: None :rtype: None :raises: ~azure.core.exceptions.HttpResponseError, ValueError .. admonition:: Example: .. literalinclude:: ../samples/chat_thread_client_sample.py :start-after: [START send_typing_notification] :end-before: [END send_typing_notification] :language: python :dedent: 8 :caption: Sending typing notification. """ return self._client.chat_thread.send_typing_notification(self._thread_id, **kwargs)
[docs] @distributed_trace def send_message( self, content, # type: str **kwargs # type: Any ): # type: (...) -> str """Sends a message to a thread. :param content: Required. Chat message content. :type content: str :param chat_message_type: The chat message type. Possible values include: "text", "html". Default: ChatMessageType.TEXT :type chat_message_type: str or ~azure.communication.chat.models.ChatMessageType :keyword str sender_display_name: The display name of the message sender. This property is used to populate sender name for push notifications. :return: str :rtype: str :raises: ~azure.core.exceptions.HttpResponseError, ValueError .. admonition:: Example: .. literalinclude:: ../samples/chat_thread_client_sample.py :start-after: [START send_message] :end-before: [END send_message] :language: python :dedent: 8 :caption: Sending a message. """ if not content: raise ValueError("content cannot be None.") chat_message_type = kwargs.pop("chat_message_type", None) if chat_message_type is None: chat_message_type = ChatMessageType.TEXT elif not isinstance(chat_message_type, ChatMessageType): try: chat_message_type = ChatMessageType.__getattr__(chat_message_type) # pylint:disable=protected-access except Exception: raise ValueError( "chat_message_type: {message_type} is not acceptable".format(message_type=chat_message_type)) if chat_message_type not in [ChatMessageType.TEXT, ChatMessageType.HTML]: raise ValueError( "chat_message_type: {message_type} can be only 'text' or 'html'".format(message_type=chat_message_type)) sender_display_name = kwargs.pop("sender_display_name", None) create_message_request = SendChatMessageRequest( content=content, type=chat_message_type, sender_display_name=sender_display_name ) send_chat_message_result = self._client.chat_thread.send_chat_message( chat_thread_id=self._thread_id, send_chat_message_request=create_message_request, **kwargs) return send_chat_message_result.id
[docs] @distributed_trace def get_message( self, message_id, # type: str **kwargs # type: Any ): # type: (...) -> ChatMessage """Gets a message by id. :param message_id: Required. The message id. :type message_id: str :return: ChatMessage :rtype: ~azure.communication.chat.ChatMessage :raises: ~azure.core.exceptions.HttpResponseError, ValueError .. admonition:: Example: .. literalinclude:: ../samples/chat_thread_client_sample.py :start-after: [START get_message] :end-before: [END get_message] :language: python :dedent: 8 :caption: Getting a message by message id. """ if not message_id: raise ValueError("message_id cannot be None.") chat_message = self._client.chat_thread.get_chat_message(self._thread_id, message_id, **kwargs) return ChatMessage._from_generated(chat_message) # pylint:disable=protected-access
[docs] @distributed_trace def list_messages( self, **kwargs # type: Any ): # type: (...) -> ItemPaged[ChatMessage] """Gets a list of messages from a thread. :keyword int results_per_page: The maximum number of messages to be returned per page. :keyword ~datetime.datetime start_time: The earliest point in time to get messages up to. The timestamp should be in RFC3339 format: ``yyyy-MM-ddTHH:mm:ssZ``. :return: An iterator like instance of ChatMessage :rtype: ~azure.core.paging.ItemPaged[~azure.communication.chat.ChatMessage] :raises: ~azure.core.exceptions.HttpResponseError, ValueError .. admonition:: Example: .. literalinclude:: ../samples/chat_thread_client_sample.py :start-after: [START list_messages] :end-before: [END list_messages] :language: python :dedent: 8 :caption: Listing messages of a chat thread. """ results_per_page = kwargs.pop("results_per_page", None) start_time = kwargs.pop("start_time", None) a = self._client.chat_thread.list_chat_messages( self._thread_id, max_page_size=results_per_page, start_time=start_time, cls=lambda objs: [ChatMessage._from_generated(x) for x in objs], # pylint:disable=protected-access **kwargs) return a
[docs] @distributed_trace def update_message( self, message_id, # type: str content=None, # type: Optional[str] **kwargs # type: Any ): # type: (...) -> None """Updates a message. :param message_id: Required. The message id. :type message_id: str :param content: Chat message content. :type content: str :return: None :rtype: None :raises: ~azure.core.exceptions.HttpResponseError, ValueError .. admonition:: Example: .. literalinclude:: ../samples/chat_thread_client_sample.py :start-after: [START update_message] :end-before: [END update_message] :language: python :dedent: 8 :caption: Updating a sent messages. """ if not message_id: raise ValueError("message_id cannot be None.") update_message_request = UpdateChatMessageRequest(content=content) return self._client.chat_thread.update_chat_message( chat_thread_id=self._thread_id, chat_message_id=message_id, update_chat_message_request=update_message_request, **kwargs)
[docs] @distributed_trace def delete_message( self, message_id, # type: str **kwargs # type: Any ): # type: (...) -> None """Deletes a message. :param message_id: Required. The message id. :type message_id: str :return: None :rtype: None :raises: ~azure.core.exceptions.HttpResponseError, ValueError .. admonition:: Example: .. literalinclude:: ../samples/chat_thread_client_sample.py :start-after: [START delete_message] :end-before: [END delete_message] :language: python :dedent: 8 :caption: Deleting a messages. """ if not message_id: raise ValueError("message_id cannot be None.") return self._client.chat_thread.delete_chat_message( chat_thread_id=self._thread_id, chat_message_id=message_id, **kwargs)
[docs] @distributed_trace def list_participants( self, **kwargs # type: Any ): # type: (...) -> ItemPaged[ChatThreadParticipant] """Gets the participants of a thread. :keyword int results_per_page: The maximum number of participants to be returned per page. :keyword int skip: Skips participants up to a specified position in response. :return: An iterator like instance of ChatThreadParticipant :rtype: ~azure.core.paging.ItemPaged[~azure.communication.chat.ChatThreadParticipant] :raises: ~azure.core.exceptions.HttpResponseError, ValueError .. admonition:: Example: .. literalinclude:: ../samples/chat_thread_client_sample.py :start-after: [START list_participants] :end-before: [END list_participants] :language: python :dedent: 8 :caption: Listing participants of chat thread. """ results_per_page = kwargs.pop("results_per_page", None) skip = kwargs.pop("skip", None) return self._client.chat_thread.list_chat_participants( self._thread_id, max_page_size=results_per_page, skip=skip, cls=lambda objs: [ChatThreadParticipant._from_generated(x) for x in objs], # pylint:disable=protected-access **kwargs)
[docs] @distributed_trace def add_participant( self, thread_participant, # type: ChatThreadParticipant **kwargs # type: Any ): # type: (...) -> None """Adds single thread participant to a thread. If participant already exist, no change occurs. If participant is added successfully, a tuple of (None, None) is expected. Failure to add participant to thread returns tuple of (chat_thread_participant, communication_error). :param thread_participant: Required. Single thread participant to be added to the thread. :type thread_participant: ~azure.communication.chat.ChatThreadParticipant :return: None :rtype: None :raises: ~azure.core.exceptions.HttpResponseError, ValueError, RuntimeError .. admonition:: Example: .. literalinclude:: ../samples/chat_thread_client_sample.py :start-after: [START add_participant] :end-before: [END add_participant] :language: python :dedent: 8 :caption: Adding single participant to chat thread. """ if not thread_participant: raise ValueError("thread_participant cannot be None.") participants = [thread_participant._to_generated()] # pylint:disable=protected-access add_thread_participants_request = AddChatParticipantsRequest(participants=participants) add_chat_participants_result = self._client.chat_thread.add_chat_participants( chat_thread_id=self._thread_id, add_chat_participants_request=add_thread_participants_request, **kwargs) response = [] if hasattr(add_chat_participants_result, 'errors') and \ add_chat_participants_result.errors is not None: response = CommunicationErrorResponseConverter._convert( # pylint:disable=protected-access participants=[thread_participant], communication_errors=add_chat_participants_result.errors.invalid_participants ) if len(response) != 0: failed_participant = response[0][0] communication_error = response[0][1] raise RuntimeError('Participant: ', failed_participant, ' failed to join thread due to: ', communication_error.message)
[docs] @distributed_trace def add_participants( self, thread_participants, # type: list[ChatThreadParticipant] **kwargs # type: Any ): # type: (...) -> list[(ChatThreadParticipant, CommunicationError)] """Adds thread participants to a thread. If participants already exist, no change occurs. If all participants are added successfully, then an empty list is returned; otherwise, a list of tuple(chat_thread_participant, communincation_error) is returned, of failed participants and its respective error :param thread_participants: Required. Thread participants to be added to the thread. :type thread_participants: list[~azure.communication.chat.ChatThreadParticipant] :return: List[Tuple(ChatThreadParticipant, CommunicationError)] :rtype: list[(~azure.communication.chat.ChatThreadParticipant, ~azure.communication.chat.CommunicationError)] :raises: ~azure.core.exceptions.HttpResponseError, ValueError, RuntimeError .. admonition:: Example: .. literalinclude:: ../samples/chat_thread_client_sample.py :start-after: [START add_participants] :end-before: [END add_participants] :language: python :dedent: 8 :caption: Adding participants to chat thread. """ if not thread_participants: raise ValueError("thread_participants cannot be None.") participants = [m._to_generated() for m in thread_participants] # pylint:disable=protected-access add_thread_participants_request = AddChatParticipantsRequest(participants=participants) add_chat_participants_result = self._client.chat_thread.add_chat_participants( chat_thread_id=self._thread_id, add_chat_participants_request=add_thread_participants_request, **kwargs) response = [] if hasattr(add_chat_participants_result, 'errors') and \ add_chat_participants_result.errors is not None: response = CommunicationErrorResponseConverter._convert( # pylint:disable=protected-access participants=thread_participants, communication_errors=add_chat_participants_result.errors.invalid_participants ) return response
[docs] @distributed_trace def remove_participant( self, user, # type: CommunicationUserIdentifier **kwargs # type: Any ): # type: (...) -> None """Remove a participant from a thread. :param user: Required. User identity of the thread participant to remove from the thread. :type user: ~azure.communication.chat.CommunicationUserIdentifier :return: None :rtype: None :raises: ~azure.core.exceptions.HttpResponseError, ValueError .. admonition:: Example: .. literalinclude:: ../samples/chat_thread_client_sample.py :start-after: [START remove_participant] :end-before: [END remove_participant] :language: python :dedent: 8 :caption: Removing participant from chat thread. """ if not user: raise ValueError("user cannot be None.") return self._client.chat_thread.remove_chat_participant( chat_thread_id=self._thread_id, participant_communication_identifier=CommunicationUserIdentifierConverter.to_identifier_model(user), **kwargs)
[docs] def close(self): # type: () -> None return self._client.close()
def __enter__(self): # type: () -> ChatThreadClient self._client.__enter__() # pylint:disable=no-member return self def __exit__(self, *args): # type: (*Any) -> None self._client.__exit__(*args) # pylint:disable=no-member