This coroutine sends a message.

        It sends a string (:class:`str`) as a text frame and a bytes-like
        object (:class:`bytes`, :class:`bytearray`, or :class:`memoryview`)
        as a binary frame.

        It also accepts an iterable or an asynchronous iterator of strings or
        bytes-like objects. Each item is treated as a message fragment and
        sent in its own frame. All items must be of the same type, or else
        :meth:`send` will raise a :exc:`TypeError` and the connection will be
        closed.

        It raises a :exc:`TypeError` for other inputs.

async def send(
        self, message: Union[Data, Iterable[Data], AsyncIterable[Data]]
    ) -> None:
        """
        This coroutine sends a message.

        It sends a string (:class:`str`) as a text frame and a bytes-like
        object (:class:`bytes`, :class:`bytearray`, or :class:`memoryview`)
        as a binary frame.

        It also accepts an iterable or an asynchronous iterator of strings or
        bytes-like objects. Each item is treated as a message fragment and
        sent in its own frame. All items must be of the same type, or else
        :meth:`send` will raise a :exc:`TypeError` and the connection will be
        closed.

        It raises a :exc:`TypeError` for other inputs.

        """
        await self.ensure_open()

        # Unfragmented message -- this case must be handled first because
        # strings and bytes-like objects are iterable.

        if isinstance(message, (str, bytes, bytearray, memoryview)):
            opcode, data = prepare_data(message)
            await self.write_frame(True, opcode, data)

        # Fragmented message -- regular iterator.

        elif isinstance(message, Iterable):

            # Work around https://github.com/python/mypy/issues/6227
            message = cast(Iterable[Data], message)

            iter_message = iter(message)

            # First fragment.
            try:
                message_chunk = next(iter_message)
            except StopIteration:
                return
            opcode, data = prepare_data(message_chunk)
            await self.write_frame(False, opcode, data)

            # Other fragments.
            for message_chunk in iter_message:
                confirm_opcode, data = prepare_data(message_chunk)
                if confirm_opcode != opcode:
                    # We're half-way through a fragmented message and we can't
                    # complete it. This makes the connection unusable.
                    self.fail_connection(1011)
                    raise TypeError("data contains inconsistent types")
                await self.write_frame(False, OP_CONT, data)

            # Final fragment.
            await self.write_frame(True, OP_CONT, b"")

        # Fragmented message -- asynchronous iterator

        elif isinstance(message, AsyncIterable):
            # aiter_message = aiter(message) without aiter
            aiter_message = type(message).__aiter__(message)

            # First fragment.
            try:
                # message_chunk = anext(aiter_message) without anext
                message_chunk = await type(aiter_message).__anext__(aiter_message)
            except StopAsyncIteration:
                return
            opcode, data = prepare_data(message_chunk)
            await self.write_frame(False, opcode, data)

            # Other fragments.
            async for message_chunk in aiter_message:
                confirm_opcode, data = prepare_data(message_chunk)
                if confirm_opcode != opcode:
                    # We're half-way through a fragmented message and we can't
                    # complete it. This makes the connection unusable.
                    self.fail_connection(1011)
                    raise TypeError("data contains inconsistent types")
                await self.write_frame(False, OP_CONT, data)

            # Final fragment.
            await self.write_frame(True, OP_CONT, b"")

        else:
            raise TypeError("data must be bytes, str, or iterable")