@@ -449,6 +449,117 @@ def consumer_name(self) -> str:
449449 """
450450 return self ._consumer .consumer_name ()
451451
452+ class Reader :
453+ """
454+ The Pulsar topic reader, used to read messages from a topic.
455+ """
456+
457+ def __init__ (self , reader : _pulsar .Reader , schema : pulsar .schema .Schema ) -> None :
458+ """
459+ Create the reader.
460+ Users should not call this constructor directly. Instead, create the
461+ reader via ``Client.create_reader``.
462+
463+ Parameters
464+ ----------
465+ reader: _pulsar.Reader
466+ The underlying Reader object from the C extension.
467+ schema: pulsar.schema.Schema
468+ The schema of the data that will be received by this reader.
469+ """
470+ self ._reader = reader
471+ self ._schema = schema
472+
473+ async def read_next (self , timeout_millis : int | None = None ) -> pulsar .Message :
474+ """
475+ Read a single message asynchronously.
476+
477+ Parameters
478+ ----------
479+ timeout_millis: int | None, optional
480+ If specified, the reader will raise an exception if a message is not
481+ available within the timeout.
482+
483+ Returns
484+ -------
485+ pulsar.Message
486+ The message received.
487+
488+ Raises
489+ ------
490+ PulsarException
491+ """
492+ future = asyncio .get_running_loop ().create_future ()
493+ if timeout_millis is None :
494+ self ._reader .read_next_async (functools .partial (_set_future , future ))
495+ else :
496+ _check_type (int , timeout_millis , 'timeout_millis' )
497+ self ._reader .read_next_async (functools .partial (_set_future , future ))
498+ msg = await future
499+ m = pulsar .Message ()
500+ m ._message = msg
501+ m ._schema = self ._schema
502+ return m
503+
504+ async def has_message_available (self ) -> bool :
505+ """
506+ Check if there is any message available to read from the current
507+ position.
508+ """
509+ future = asyncio .get_running_loop ().create_future ()
510+ self ._reader .has_message_available_async (functools .partial (_set_future , future ))
511+ return await future
512+
513+ async def seek (self , messageid : Union [pulsar .MessageId , int ]) -> None :
514+ """
515+ Reset this reader to a specific message id or publish timestamp
516+ asynchronously.
517+
518+ Parameters
519+ ----------
520+ messageid : MessageId or int
521+ The message id for seek, OR an integer event time (timestamp) to
522+ seek to.
523+
524+ Raises
525+ ------
526+ PulsarException
527+ """
528+ future = asyncio .get_running_loop ().create_future ()
529+ if isinstance (messageid , pulsar .MessageId ):
530+ msg_id = messageid ._msg_id
531+ elif isinstance (messageid , int ):
532+ msg_id = messageid
533+ else :
534+ raise ValueError (f"invalid messageid type { type (messageid )} " )
535+ self ._reader .seek_async (msg_id , functools .partial (_set_future , future , value = None ))
536+ await future
537+
538+ async def close (self ) -> None :
539+ """
540+ Close the reader asynchronously.
541+
542+ Raises
543+ ------
544+ PulsarException
545+ """
546+ future = asyncio .get_running_loop ().create_future ()
547+ self ._reader .close_async (functools .partial (_set_future , future , value = None ))
548+ await future
549+
550+ def topic (self ) -> str :
551+ """
552+ Return the topic this reader is reading from.
553+ """
554+ return self ._reader .topic ()
555+
556+ def is_connected (self ) -> bool :
557+ """
558+ Check if the reader is connected or not.
559+ """
560+ return self ._reader .is_connected ()
561+
562+
452563class Client :
453564 """
454565 The asynchronous version of `pulsar.Client`.
@@ -777,6 +888,93 @@ async def subscribe(self, topic: Union[str, List[str]],
777888 schema .attach_client (self ._client )
778889 return Consumer (await future , schema )
779890
891+ # pylint: disable=too-many-arguments,too-many-locals,too-many-positional-arguments
892+ async def create_reader (self , topic : str ,
893+ start_message_id : Union [pulsar .MessageId , _pulsar .MessageId ],
894+ schema : pulsar .schema .Schema | None = None ,
895+ receiver_queue_size : int = 1000 ,
896+ reader_name : str | None = None ,
897+ subscription_role_prefix : str | None = None ,
898+ is_read_compacted : bool = False ,
899+ crypto_key_reader : pulsar .CryptoKeyReader | None = None ,
900+ start_message_id_inclusive : bool = False ,
901+ crypto_failure_action : ConsumerCryptoFailureAction =
902+ ConsumerCryptoFailureAction .FAIL ,
903+ ) -> Reader :
904+ """
905+ Create a reader on a particular topic.
906+
907+ Parameters
908+ ----------
909+ topic: str
910+ The name of the topic.
911+ start_message_id: MessageId or _pulsar.MessageId
912+ The initial reader positioning is done by specifying a message id.
913+ The options are:
914+
915+ * ``MessageId.earliest``: Start reading from the earliest message
916+ available in the topic.
917+ * ``MessageId.latest``: Start reading from the end topic, only
918+ getting messages published after the reader was created.
919+ * ``MessageId``: When passing a particular message id, the reader
920+ will position itself on that specific position.
921+ schema: pulsar.schema.Schema | None, default=None
922+ Define the schema of the data that will be received by this reader.
923+ receiver_queue_size: int, default=1000
924+ Sets the size of the reader receive queue.
925+ reader_name: str | None, default=None
926+ Sets the reader name.
927+ subscription_role_prefix: str | None, default=None
928+ Sets the subscription role prefix.
929+ is_read_compacted: bool, default=False
930+ Selects whether to read the compacted version of the topic.
931+ crypto_key_reader: pulsar.CryptoKeyReader | None, default=None
932+ Symmetric encryption class implementation.
933+ start_message_id_inclusive: bool, default=False
934+ Set the reader to include the startMessageId or given position of
935+ any reset operation like Reader.seek.
936+ crypto_failure_action: ConsumerCryptoFailureAction, \
937+ default=ConsumerCryptoFailureAction.FAIL
938+ Set the behavior when the decryption fails.
939+
940+ Returns
941+ -------
942+ Reader
943+ The reader created
944+
945+ Raises
946+ ------
947+ PulsarException
948+ """
949+ if schema is None :
950+ schema = pulsar .schema .BytesSchema ()
951+
952+ if isinstance (start_message_id , pulsar .MessageId ):
953+ start_message_id = start_message_id ._msg_id
954+
955+ _check_type (_pulsar .MessageId , start_message_id , 'start_message_id' )
956+
957+ conf = _pulsar .ReaderConfiguration ()
958+ conf .receiver_queue_size (receiver_queue_size )
959+ if reader_name is not None :
960+ conf .reader_name (reader_name )
961+ if subscription_role_prefix is not None :
962+ conf .subscription_role_prefix (subscription_role_prefix )
963+ conf .schema (schema .schema_info ())
964+ conf .read_compacted (is_read_compacted )
965+ if crypto_key_reader is not None :
966+ conf .crypto_key_reader (crypto_key_reader .cryptoKeyReader )
967+ conf .start_message_id_inclusive (start_message_id_inclusive )
968+ conf .crypto_failure_action (crypto_failure_action )
969+
970+ future = asyncio .get_running_loop ().create_future ()
971+ self ._client .create_reader_async (
972+ topic , start_message_id , conf , functools .partial (_set_future , future )
973+ )
974+ reader = await future
975+ schema .attach_client (self ._client )
976+ return Reader (reader , schema )
977+
780978 def shutdown (self ) -> None :
781979 """
782980 Shutdown the client and all the associated producers and consumers
0 commit comments