@@ -449,6 +449,107 @@ 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 ) -> pulsar .Message :
474+ """
475+ Read a single message asynchronously.
476+
477+ Returns
478+ -------
479+ pulsar.Message
480+ The message received.
481+
482+ Raises
483+ ------
484+ PulsarException
485+ """
486+ future = asyncio .get_running_loop ().create_future ()
487+ self ._reader .read_next_async (functools .partial (_set_future , future ))
488+ msg = await future
489+ m = pulsar .Message ()
490+ m ._message = msg
491+ m ._schema = self ._schema
492+ return m
493+
494+ async def has_message_available (self ) -> bool :
495+ """
496+ Check if there is any message available to read from the current
497+ position.
498+ """
499+ future = asyncio .get_running_loop ().create_future ()
500+ self ._reader .has_message_available_async (functools .partial (_set_future , future ))
501+ return await future
502+
503+ async def seek (self , messageid : Union [pulsar .MessageId , int ]) -> None :
504+ """
505+ Reset this reader to a specific message id or publish timestamp
506+ asynchronously.
507+
508+ Parameters
509+ ----------
510+ messageid : MessageId or int
511+ The message id for seek, OR an integer event time (timestamp) to
512+ seek to.
513+
514+ Raises
515+ ------
516+ PulsarException
517+ """
518+ future = asyncio .get_running_loop ().create_future ()
519+ if isinstance (messageid , pulsar .MessageId ):
520+ msg_id = messageid ._msg_id
521+ elif isinstance (messageid , int ):
522+ msg_id = messageid
523+ else :
524+ raise ValueError (f"invalid messageid type { type (messageid )} " )
525+ self ._reader .seek_async (msg_id , functools .partial (_set_future , future , value = None ))
526+ await future
527+
528+ async def close (self ) -> None :
529+ """
530+ Close the reader asynchronously.
531+
532+ Raises
533+ ------
534+ PulsarException
535+ """
536+ future = asyncio .get_running_loop ().create_future ()
537+ self ._reader .close_async (functools .partial (_set_future , future , value = None ))
538+ await future
539+
540+ def topic (self ) -> str :
541+ """
542+ Return the topic this reader is reading from.
543+ """
544+ return self ._reader .topic ()
545+
546+ def is_connected (self ) -> bool :
547+ """
548+ Check if the reader is connected or not.
549+ """
550+ return self ._reader .is_connected ()
551+
552+
452553class Client :
453554 """
454555 The asynchronous version of `pulsar.Client`.
@@ -777,6 +878,93 @@ async def subscribe(self, topic: Union[str, List[str]],
777878 schema .attach_client (self ._client )
778879 return Consumer (await future , schema )
779880
881+ # pylint: disable=too-many-arguments,too-many-locals,too-many-positional-arguments
882+ async def create_reader (self , topic : str ,
883+ start_message_id : Union [pulsar .MessageId , _pulsar .MessageId ],
884+ schema : pulsar .schema .Schema | None = None ,
885+ receiver_queue_size : int = 1000 ,
886+ reader_name : str | None = None ,
887+ subscription_role_prefix : str | None = None ,
888+ is_read_compacted : bool = False ,
889+ crypto_key_reader : pulsar .CryptoKeyReader | None = None ,
890+ start_message_id_inclusive : bool = False ,
891+ crypto_failure_action : ConsumerCryptoFailureAction =
892+ ConsumerCryptoFailureAction .FAIL ,
893+ ) -> Reader :
894+ """
895+ Create a reader on a particular topic.
896+
897+ Parameters
898+ ----------
899+ topic: str
900+ The name of the topic.
901+ start_message_id: MessageId or _pulsar.MessageId
902+ The initial reader positioning is done by specifying a message id.
903+ The options are:
904+
905+ * ``MessageId.earliest``: Start reading from the earliest message
906+ available in the topic.
907+ * ``MessageId.latest``: Start reading from the end topic, only
908+ getting messages published after the reader was created.
909+ * ``MessageId``: When passing a particular message id, the reader
910+ will position itself on that specific position.
911+ schema: pulsar.schema.Schema | None, default=None
912+ Define the schema of the data that will be received by this reader.
913+ receiver_queue_size: int, default=1000
914+ Sets the size of the reader receive queue.
915+ reader_name: str | None, default=None
916+ Sets the reader name.
917+ subscription_role_prefix: str | None, default=None
918+ Sets the subscription role prefix.
919+ is_read_compacted: bool, default=False
920+ Selects whether to read the compacted version of the topic.
921+ crypto_key_reader: pulsar.CryptoKeyReader | None, default=None
922+ Symmetric encryption class implementation.
923+ start_message_id_inclusive: bool, default=False
924+ Set the reader to include the startMessageId or given position of
925+ any reset operation like Reader.seek.
926+ crypto_failure_action: ConsumerCryptoFailureAction, \
927+ default=ConsumerCryptoFailureAction.FAIL
928+ Set the behavior when the decryption fails.
929+
930+ Returns
931+ -------
932+ Reader
933+ The reader created
934+
935+ Raises
936+ ------
937+ PulsarException
938+ """
939+ if schema is None :
940+ schema = pulsar .schema .BytesSchema ()
941+
942+ if isinstance (start_message_id , pulsar .MessageId ):
943+ start_message_id = start_message_id ._msg_id
944+
945+ _check_type (_pulsar .MessageId , start_message_id , 'start_message_id' )
946+
947+ conf = _pulsar .ReaderConfiguration ()
948+ conf .receiver_queue_size (receiver_queue_size )
949+ if reader_name is not None :
950+ conf .reader_name (reader_name )
951+ if subscription_role_prefix is not None :
952+ conf .subscription_role_prefix (subscription_role_prefix )
953+ conf .schema (schema .schema_info ())
954+ conf .read_compacted (is_read_compacted )
955+ if crypto_key_reader is not None :
956+ conf .crypto_key_reader (crypto_key_reader .cryptoKeyReader )
957+ conf .start_message_id_inclusive (start_message_id_inclusive )
958+ conf .crypto_failure_action (crypto_failure_action )
959+
960+ future = asyncio .get_running_loop ().create_future ()
961+ self ._client .create_reader_async_v2 (
962+ topic , start_message_id , conf , functools .partial (_set_future_v2 , future )
963+ )
964+ reader = await future
965+ schema .attach_client (self ._client )
966+ return Reader (reader , schema )
967+
780968 def shutdown (self ) -> None :
781969 """
782970 Shutdown the client and all the associated producers and consumers
0 commit comments