1+ import json
2+
3+ import logging
4+ import time
5+ from ..config .config import TopicConfig
6+ from ..resource_errors import ExceptionHandler
7+ from concurrent .futures import ThreadPoolExecutor
8+ from .abstract .topic_abstract import TopicAbstract
9+ from ..queue .models .queue_message import QueueMessage
10+ from azure .servicebus import ServiceBusClient , ServiceBusMessage
11+ from azure .servicebus import AutoLockRenewer
12+ import concurrent .futures as cf
13+
14+ logging .basicConfig (format = '%(asctime)s %(levelname)-8s %(message)s' , datefmt = '%Y-%m-%d %H:%M:%S' )
15+ logger = logging .getLogger ('AzureTopic' )
16+ logger .setLevel (logging .INFO )
17+
18+
19+ """
20+ AzureTopic class represents a topic in Azure Service Bus.
21+ Attributes:
22+ topic (str): The name of the topic.
23+ client (ServiceBusClient): The ServiceBusClient object used to interact with the Service Bus.
24+ max_concurrent_messages (int): The maximum number of concurrent messages to process.
25+ topic_name (str): The name of the topic.
26+ publisher (TopicSender): The TopicSender object used to send messages to the topic.
27+ executor (ThreadPoolExecutor): The ThreadPoolExecutor object used to execute callback functions.
28+ internal_count (int): The internal count of concurrent messages being processed.
29+ lock_renewal (AutoLockRenewer): The AutoLockRenewer object used to renew message locks.
30+ max_renewal_duration (int): The maximum duration in seconds to renew a message lock.
31+ wait_time_for_message (int): The maximum wait time in seconds to receive messages.
32+ Methods:
33+ publish(data: QueueMessage) -> None:
34+ Publishes a message to the topic.
35+ subscribe(subscription: str, callback) -> None:
36+ Subscribes to a subscription of the topic and processes incoming messages.
37+ internal_callback(message, callbackfn) -> ServiceBusMessage:
38+ Internal callback function that processes a message and invokes the callback function.
39+ settle_message(x: cf.Future) -> None:
40+ Sets the message as completed and updates the internal count.
41+ """
42+ class AzureTopic (TopicAbstract ):
43+ def __init__ (self , config : TopicConfig = None , topic_name = None , max_concurrent_messages :int = 1 ):
44+ self .topic = topic_name
45+ self .client = ServiceBusClient .from_connection_string (conn_str = config .connection_string , retry_total = 10 , retry_backoff_factor = 1 , retry_backoff_max = 30 )
46+ self .max_concurrent_messages = max_concurrent_messages
47+ self .topic_name = topic_name
48+ self .publisher = self .client .get_topic_sender (topic_name = topic_name )
49+ self .executor = ThreadPoolExecutor (max_workers = max_concurrent_messages )
50+ self .internal_count = 0
51+ self .lock_renewal = AutoLockRenewer (max_workers = 4 )
52+ self .max_renewal_duration = 86400 # Renew the message upto 1 day
53+ self .wait_time_for_message = 5
54+
55+
56+ def publish (self , data : QueueMessage ):
57+ """
58+ Publishes a message to the topic.
59+ Args:
60+ data (QueueMessage): The message to publish.
61+
62+ """
63+ message = QueueMessage .to_dict (data )
64+ self .publisher .send_messages (ServiceBusMessage (json .dumps (message )))
65+
66+ def subscribe (self , subscription : str , callback ):
67+
68+ """
69+ Subscribes to a subscription of the topic and processes incoming messages.
70+ Args:
71+ subscription (str): The name of the subscription to subscribe to.
72+ callback (function): The callback function to invoke for each message.
73+ """
74+ self .receiver = self .client .get_subscription_receiver (topic_name = self .topic_name , subscription_name = subscription )
75+ while True :
76+ try :
77+ to_receive = (self .max_concurrent_messages - self .internal_count )
78+ if to_receive > 0 :
79+ messages = self .receiver .receive_messages (max_message_count = to_receive , max_wait_time = self .wait_time_for_message )
80+ if not messages or len (messages ) == 0 :
81+ continue
82+ for message in messages :
83+ self .lock_renewal .register (self .receiver , message , max_lock_renewal_duration = self .max_renewal_duration )
84+ execution_task = self .executor .submit (self .internal_callback , message , callback )
85+ execution_task .add_done_callback (lambda x : self .settle_message (x ))
86+ else :
87+ time .sleep (self .wait_time_for_message )
88+ except Exception as e :
89+ logger .error (f'Error in receiving messages: { e } ' )
90+
91+
92+ def internal_callback (self , message , callbackfn ):
93+ """
94+ Internal callback function that processes a message and invokes the callback function.
95+ Args:
96+ message (ServiceBusMessage): The message to process.
97+ callbackfn (function): The callback function to invoke.
98+ Returns:
99+ ServiceBusMessage: The processed message.
100+ """
101+ try :
102+ self .internal_count += 1
103+ queue_message = QueueMessage .data_from (str (message ))
104+ callbackfn (queue_message )
105+ except Exception as e :
106+ logger .error (f'Error in processing message: { e } ' )
107+ finally :
108+ return message
109+
110+
111+ def settle_message (self , x : cf .Future ):
112+ """
113+ Sets the message as completed and updates the internal count.
114+ Args:
115+ x (cf.Future): The future object representing the message processing.
116+ """
117+ self .internal_count -= 1
118+ incoming_message = x .result ()
119+ self .receiver .complete_message (incoming_message )
120+ return
121+
122+
0 commit comments