I'm using the Python Azure ServiceBus package and have the following snippet of code:
# receiver.py
import logging
logger = logging.getLogger("test")
def receive_message(
connection_str,
queue_name
) -> None:
"""
Call Azure ServiceBus API to retrieve message from a queue
"""
with ServiceBusClient.from_connection_string(
connection_str, logging_enable=True
) as servicebus_client:
with servicebus_client.get_queue_receiver(
queue_name=queue_name,
max_wait_time=20,
receive_mode=ServiceBusReceiveMode.PEEK_LOCK,
) as receiver:
for message in receiver:
logger.debug(f"Received message {message}")
I'm attempting to write unit tests for this function, and want to be able to mock out the recevier. Here is my attempt at writing the unit test, which fails because I can't figure out how to get the test to enter the for message in receiver block.
# test_receiver.py
#patch("receiver.ServiceBusClient")
#patch("receiver.logger")
def test_receive_message(mock_logger, mock_svcbus_client):
# Figure out how to mock
mock_svcbus_client.from_connection_string.return_value.get_queue_receiver.return_value = iter(["message"])
receive_message("mock_connection_str", "mock_q_name")
# Assertion fails
mock_logger.return_value.debug.assert_called_once()
You can try from mocks import MockReceivedMessage, MockReceiver to mock the receiver
Example 1:
class MockReceivedMessage(ServiceBusReceivedMessage):
def __init__(self, prevent_renew_lock=False, exception_on_renew_lock=False, **kwargs):
self._lock_duration = kwargs.get("lock_duration", 2)
self._raw_amqp_message = None
self._received_timestamp_utc = utc_now()
self.locked_until_utc = self._received_timestamp_utc + timedelta(seconds=self._lock_duration)
self._settled = False
self._receiver = MockReceiver()
self._prevent_renew_lock = prevent_renew_lock
self._exception_on_renew_lock = exception_on_renew_lock
Example 2:
def test_queue_message_receive_and_delete(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs):
with ServiceBusClient.from_connection_string(
servicebus_namespace_connection_string, logging_enable=False) as sb_client:
with sb_client.get_queue_sender(servicebus_queue.name) as sender:
message = ServiceBusMessage("Receive and delete test")
sender.send_messages(message)
with sb_client.get_queue_receiver(servicebus_queue.name,
receive_mode=ServiceBusReceiveMode.RECEIVE_AND_DELETE) as receiver:
messages = receiver.receive_messages(max_wait_time=10)
assert len(messages) == 1
message = messages[0]
print_message(_logger, message)
with pytest.raises(ValueError):
receiver.complete_message(message)
with pytest.raises(ValueError):
receiver.abandon_message(message)
with pytest.raises(ValueError):
receiver.defer_message(message)
with pytest.raises(ValueError):
receiver.dead_letter_message(message)
with pytest.raises(ValueError):
receiver.renew_message_lock(message)
time.sleep(10)
with sb_client.get_queue_receiver(servicebus_queue.name) as receiver:
messages = receiver.receive_messages(max_wait_time=10)
for m in messages:
print_message(_logger, m)
assert len(messages) == 0
You can refer to mocks.py
and test_queues.py
If you still have doubt, You can open an issue on GitHub: azure-sdk-for-python
Related
I am trying to build agents programmatically and have been following this example. And I could see topics and agents when I look at faust -A <> agents that have been generated at the start of the script. The agent name is something like {topic}_agent as per example. All these agents respectively reach out to some rest api. Here is the code.
def create_agent(next_topic, model_metadata: Dict):
"""
creation of a single agent.
`start_topic`: str
Just a string that you can use in other functions
to figure out how messages in that topic can be
transformed
`next_topic`: faust.topics.Topic
A faust `app.topic` instance
"""
async def agent(stream):
"""Call domino model and return response
"""
async for message in stream:
domino_req = {"data": message.asdict()}
app.logger.info(domino_req)
response = requests.post(
model_metadata['url'],
auth=(
model_metadata['auth'],
model_metadata['auth'],
),
json=domino_req,
)
if response.status_code == 200:
answer = response.json()
await next_topic.send(answer['result'])
else:
app.logger.error(response.reason)
# app.logger.info(f"NEW Agent Created: ## Agent - {consumer} ##")
return agent
def agents(registry_response):
"""
configuration of multiple agents
"""
agents = []
for key, value in registry_response.items():
""" `topic`: app.topic instance """
agents.append(
# `topic.start`: str
# `topic.next`: faust.topics.Topic
(create_agent(next_topic= all_responses, model_metadata = value),key)
)
return agents
def attach_agent(agent, topic):
""" Attach the agent to the Faust app """
# `topic.faust`: faust.topics.Topic
# it is equivalent to `app.topic(topic.start)`
print("hello")
app.agent(channel=app.topic(topic), name=f"{topic}_agent")(agent)
# new_agent.start()
# app.logger.info("hello")
# app.logger.info(new_agent)
# app.logger.info(new_agent.info())
# app.logger.info(app.agents)
# #app.task
# async def get_model_registry():
# """
# Create topics and agents for the initial set of models present in the registry
# """
app.logger.info('APP STARTED')
app.logger.info('Fetching Models from Model Registry')
#TODO: Call the Model Registry and process it
#Just mocking the registry
registry_response = initial_model_registry_metadata
app.logger.info(f'Number of Models Found {len(registry_response)}')
for agent,topic in agents(registry_response):
attach_agent(agent, topic)
#app.page('/model/{key_ai_model}')
class frontdoor(View):
async def get(self, request: Request) -> Response:
return self.json({'key_ai_model': key_ai_model})
async def post(self, request: Request, key_ai_model: str) -> Response:
request_id = str(uuid.uuid4())
src = await request.json()
msg = GdeltRequest(**src)
app.logger.info(msg)
await gdelt_agent.cast(msg, key=request_id)
return self.json({'request_id': request_id, 'key_ai_model': key_ai_model})
#app.agent(all_responses)
async def print_responses(stream):
async for message in stream:
print(message)
However, when I post something I am getting an error - NameError: name 'gdelt_agent' is not defined. Any help here will be much appreciated.
I have been able to figure this out by assigning the agents as a value in dicitonary and later using it to cast messages to the agent.
agents_dict = {}
topics_dict ={}
def attach_agent(agent, topic):
""" Attach the agent to the Faust app """
# `topic.faust`: faust.topics.Topic
# it is equivalent to `app.topic(topic.start)`
print("hello")
topics_dict[topic] = app.topic(topic)
agents_dict[topic] = app.agent(channel=topics_dict[topic], name=f"{topic}_agent")(agent)
agents_dict[topic].start()
Thank you everyone.
Can someone help me figure out why I am getting this exception please? Even though I have the suscribe key configured, it seems like it is not.
Here is the exception: "Exception in subscribe loop: Subscribe key not configured
reconnection policy is disabled, please handle reconnection manually."
import time
from pubnub.pubnub import PubNub
from pubnub.pnconfiguration import PNConfiguration
from pubnub.callbacks import SubscribeCallback
from backend.blockchain.block import Block
pnconfig = PNConfiguration()
pnconfig.suscribe_key = 'sub-c-6d0fe192-dee4-11ea-9b19-...'
pnconfig.publish_key = 'pub-c-c3553c68-bf24-463c-ae43-...'
CHANNELS = {
'TEST': 'TEST',
'BLOCK': 'BLOCK'
}
class Listener(SubscribeCallback):
def __init__(self, blockchain):
self.blockchain = blockchain
def message(self, pubnub, message_object):
print('\n-- Channel: {message_object.channel} | Message: {message_object.message}')
if message_object.channel == CHANNELS['BLOCK']:
block = Block.from_json(message_object.message)
potential_chain = self.blockchain.chain[:]
potential_chain.append(block)
try:
self.blockchain.replace_chain(potential_chain)
print('\n -- Successfully replaced the local chain')
except Exception as e:
print('\n -- Did not replace chain: {e}')
class PubSub():
"""
Handles the publish/subscribe layer of the application.
Provides communication between the nodes of the blockchain network.
"""
def __init__(self, blockchain):
self.pubnub = PubNub(pnconfig)
self.pubnub.subscribe().channels(CHANNELS.values()).execute()
self.pubnub.add_listener(Listener(blockchain))
def publish(self, channel, message):
"""
Publish the message object to the channel.
"""
self.pubnub.publish().channel(channel).message(message).sync()
def broadcast_block(self, block):
"""
Broadcast a block object to all nodes.
"""
self.publish(CHANNELS['BLOCK'], block.to_json())
def main():
pubsub = PubSub()
time.sleep(1)
pubsub.publish(CHANNELS['TEST'], { 'foo': 'bar' })
if __name__ == '__main__':
main()
I wasn't able to get the error "Exception in subscribe loop: Subscribe key not configured reconnection policy is disabled, please handle reconnection manually.".
I used the code block:
pnconfig = PNConfiguration()
pnconfig.subscribe_key = "my_subkey"
pnconfig.publish_key = "my_pubkey"
pnconfig.ssl = True
pnconfig.uuid = "my_custom_uuid"
pnconfig.reconnect_policy = "LINEAR"
pubnub = PubNub(pnconfig)
from page: https://www.pubnub.com/docs/python/api-reference-configuration
and added:
PNReconnectionPolicy.NONE
from page: https://www.pubnub.com/docs/python/api-reference-configuration
I have a custom module written called sqs.py. The script will do the following:
Get a message from AWS SQS
Get the AWS S3 path to delete
Delete the path
Send a confirmation email to the user
I'm trying to write unit tests for this module that will verify the code will execute as expected and that it will raise exceptions when they do occur.
This means I will need to mock the response from Boto3 calls that I make. My problem is that the code will first establish the SQS client to obtain the message and then a second call to establish the S3 client. I'm not sure how to mock these 2 independent calls and be able to fake a response so I can test my script's functionality. Perhaps my approach is incorrect. At any case, any advice on how to do this properly is appreciated.
Here's how the code looks like:
import boto3
import json
import os
import pprint
import time
import asyncio
import logging
from send_email import send_email
queue_url = 'https://xxxx.queue.amazonaws.com/1234567890/queue'
def shutdown(message):
""" Sends shutdown command to OS """
os.system(f'shutdown +5 "{message}"')
def send_failure_email(email_config: dict, error_message: str):
""" Sends email notification to user with error message attached. """
recipient_name = email_config['recipient_name']
email_config['subject'] = 'Subject: Restore Failed'
email_config['message'] = f'Hello {recipient_name},\n\n' \
+ 'We regret that an error has occurred during the restore process. ' \
+ 'Please try again in a few minutes.\n\n' \
+ f'Error: {error_message}.\n\n' \
try:
send_email(email_config)
except RuntimeError as error_message:
logging.error(f'ERROR: cannot send email to user. {error_message}')
async def restore_s3_objects(s3_client: object, p_bucket_name: str, p_prefix: str):
"""Attempts to restore objects specified by p_bucket_name and p_prefix.
Returns True if restore took place, false otherwise.
"""
is_truncated = True
key_marker = None
key = ''
number_of_items_restored = 0
has_restore_occured = False
logging.info(f'performing restore for {p_bucket_name}/{p_prefix}')
try:
while is_truncated == True:
if not key_marker:
version_list = s3_client.list_object_versions(
Bucket = p_bucket_name,
Prefix = p_prefix)
else:
version_list = s3_client.list_object_versions(
Bucket = p_bucket_name,
Prefix = p_prefix,
KeyMarker = key_marker)
if 'DeleteMarkers' in version_list:
logging.info('found delete markers')
delete_markers = version_list['DeleteMarkers']
for d in delete_markers:
if d['IsLatest'] == True:
key = d['Key']
version_id = d['VersionId']
s3_client.delete_object(
Bucket = p_bucket_name,
Key = key,
VersionId = version_id
)
number_of_items_restored = number_of_items_restored + 1
is_truncated = version_list['IsTruncated']
logging.info(f'is_truncated: {is_truncated}')
if 'NextKeyMarker' in version_list:
key_marker = version_list['NextKeyMarker']
if number_of_items_restored > 0:
has_restore_occured = True
return has_restore_occured
except Exception as error_message:
raise RuntimeError(error_message)
async def main():
if 'AWS_ACCESS_KEY_ID' in os.environ \
and 'AWS_SECRET_ACCESS_KEY' in os.environ \
and os.environ['AWS_ACCESS_KEY_ID'] != '' \
and os.environ['AWS_SECRET_ACCESS_KEY'] != '':
sqs_client = boto3.client(
'sqs',
aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'],
aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'],
verify=False
)
s3_client = boto3.client(
's3',
aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'],
aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'],
verify=False
)
else:
sqs_client = boto3.client(
'sqs',
verify=False,
)
s3_client = boto3.client(
's3',
verify=False,
)
received_message = sqs_client.receive_message(
QueueUrl=queue_url,
AttributeNames=['All'],
VisibilityTimeout=10,
WaitTimeSeconds=20, # Wait up to 20 seconds for a message to arrive
)
if 'Messages' in received_message \
and len(received_message['Messages']) > 0:
# NOTE: Initialize email configuration
receipient_email = 'support#example.com'
username = receipient_email.split('#')[0]
fullname_length = len(username.split('.'))
fullname = f"{username.split('.')[0]}" # Group name / First name only
if (fullname_length == 2): # First name and last name available
fullname = f"{username.split('.')[0]} {username.split('.')[1]}"
fullname = fullname.title()
email_config = {
'destination': receipient_email,
'recipient_name': fullname,
'subject': 'Subject: Restore Complete',
'message': ''
}
try:
receipt_handle = received_message['Messages'][0]['ReceiptHandle']
except Exception as error_message:
logging.error(error_message)
send_failure_email(email_config, error_message)
shutdown(f'{error_message}')
try:
data = received_message['Messages'][0]['Body']
data = json.loads(data)
logging.info('A SQS message for a restore has been received.')
except Exception as error_message:
message = f'Unable to obtain and parse message body. {error_message}'
logging.error(message)
send_failure_email(email_config, message)
shutdown(f'{error_message}')
try:
bucket = data['bucket']
prefix = data['prefix']
except Exception as error_message:
message = f'Retrieving bucket name and prefix failed. {error_message}'
logging.error(message)
send_failure_email(email_config, message)
shutdown(f'{error_message}')
try:
logging.info(f'Initiating restore for path: {bucket}/{prefix}')
restore_was_performed = await asyncio.create_task(restore_s3_objects(s3_client, bucket, prefix))
if restore_was_performed is True:
email_config['message'] = f'Hello {fullname},\n\n' \
+ f'The files in the path \'{bucket}/{prefix}\' have been restored. ' \
send_email(email_config)
logging.info('Restore complete. Shutting down.')
else:
logging.info('Path does not require restore. Shutting down.')
shutdown(f'shutdown +5 "Restore successful! System will shutdown in 5 mins"')
except Exception as error_message:
message = f'File restoration failed. {error_message}'
logging.error(message)
send_failure_email(email_config, message)
shutdown(f'{error_message}')
try:
sqs_client.delete_message(
QueueUrl=queue_url,
ReceiptHandle=receipt_handle,
)
except Exception as error_message:
message = f'Deleting restore session from SQS failed. {error_message}'
logging.error(message)
send_failure_email(email_config, message)
shutdown(f'{error_message}')
if __name__ == '__main__':
logging.basicConfig(filename='restore.log',level=logging.INFO)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
The only way I was able to mock Boto3 is rebuilding a small class that represents the actual method structure. This is because Boto3 uses dynamic methods and all the resource level methods are created at runtime.
This might not be industry standard but I wasn't able to get any of the methods I found on the internet to work most of the time and this worked pretty well for me and requires minimal effort (comparing to some of the solutions I found).
class MockClient:
def __init__(self, region_name, aws_access_key_id, aws_secret_access_key):
self.region_name = region_name
self.aws_access_key_id = aws_access_key_id
self.aws_secret_access_key = aws_secret_access_key
self.MockS3 = MockS3()
def client(self, service_name, **kwargs):
return self.MockS3
class MockS3:
def __init__(self):
self.response = None # Test your mock data from S3 here
def list_object_versions(self, **kwargs):
return self.response
class S3TestCase(unittest.TestCase):
def test_restore_s3_objects(self):
# Given
bucket = "testBucket" # Test this to something that somewahat realistic
prefix = "some/prefix" # Test this to something that somewahat realistic
env_vars = mock.patch.dict(os.environ, {"AWS_ACCESS_KEY_ID": "abc",
"AWS_SECRET_ACCESS_KEY": "def"})
env_vars.start()
# initialising the Session can be tricy since it has to be imported from
# the module/file that creates the session on actual code rather than
# where's a Session code is. In this case you might have to import from
# main rather than boto3.
boto3.session.Session = mock.Mock(side_effect=[
MockClient(region_name='eu-west-1',
aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'],
aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'])])
s3_client = boto3.client('s3', verify=False)
# When
has_restore_occured = restore_s3_objects(s3_client, bucket, prefix)
# Then
self.assertEqual(has_restore_occured, False) # your expected result set
env_vars.stop()
I'm using confluent_kafka package for working with Kafka.
I create topic in this way:
from confluent_kafka import avro
from confluent_kafka.avro import AvroProducer
def my_producer():
bootstrap_servers=['my_adress.com:9092',
'my_adress.com:9092']
value_schema = avro.load('/home/ValueSchema.avsc')
avroProducer = AvroProducer({
'bootstrap.servers': bootstrap_servers[0]+','+bootstrap_servers[1],
'schema.registry.url':'http://my_adress.com:8081',
},
default_value_schema=value_schema
)
for i in range(0, 25000):
value = {"name":"Yuva","favorite_number":10,"favorite_color":"green","age":i*2}
avroProducer.produce(topic='my_topik14', value=value)
avroProducer.flush(0)
print('Finished!')
if __name__ == '__main__':
my_producer()
It works. (this get 24820 messages instead of 25000 by the way...)
We can check it:
kafka-run-class kafka.tools.GetOffsetShell --broker-list my_adress.com:9092 --topic my_topik14
my_topik14:0:24819
Now I want to consume:
from confluent_kafka import KafkaError
from confluent_kafka.avro import AvroConsumer
from confluent_kafka.avro.serializer import SerializerError
bootstrap_servers=['my_adress.com:9092',
'my_adress.com:9092']
c = AvroConsumer(
{'bootstrap.servers': bootstrap_servers[0]+','+bootstrap_servers[1],
'group.id': 'avroneversleeps',
'schema.registry.url': 'http://my_adress.com:8081',
'api.version.request': True,
'fetch.min.bytes': 100000,
'consume.callback.max.messages':1000,
'batch.num.messages':2
})
c.subscribe(['my_topik14'])
running = True
while running:
msg = None
try:
msg = c.poll(0.1)
if msg:
if not msg.error():
print(msg.value())
c.commit(msg)
elif msg.error().code() != KafkaError._PARTITION_EOF:
print(msg.error())
running = False
else:
print("No Message!! Happily trying again!!")
except SerializerError as e:
print("Message deserialization failed for %s: %s" % (msg, e))
running = False
c.commit()
c.close()
But there is a problem:
I read messages just one by one.
My question is How to read batch of messages?
I tried different parameters in Consumer config but they didn't cnahge anything!
Also I found this question on SO and tried the same parameters - it still doesn't work.
Also read this. But this is against the previous link...
You can do it using consume([num_messages=1][, timeout=-1]) method. API ref. here:
For Consumer:
https://docs.confluent.io/current/clients/confluent-kafka-python/index.html#confluent_kafka.Consumer.consume
For AvroConsumer:
https://docs.confluent.io/current/clients/confluent-kafka-python/index.html?highlight=avroconsumer#confluent_kafka.Consumer.consume
More about the issue here:
https://github.com/confluentinc/confluent-kafka-python/issues/252
AvroConsumer have no consume method. But it is easy to make my own implementation of this method as there is in Consume class (parent of AvroConsumer).
Here is the code:
def consume_batch(self, num_messages=1, timeout=None):
"""
This is an overriden method from confluent_kafka.Consumer class. This handles batch of message
deserialization using avro schema
:param int num_messages: number of messages to read in one batch (default=1)
:param float timeout: Poll timeout in seconds (default: indefinite)
:returns: list of messages objects with deserialized key and value as dict objects
:rtype: Message
"""
messages_out = []
if timeout is None:
timeout = -1
messages = super(AvroConsumer, self).consume(num_messages=num_messages, timeout=timeout)
if messages is None:
return None
else:
for m in messages:
if not m.value() and not m.key():
return messages
if not m.error():
if m.value() is not None:
decoded_value = self._serializer.decode_message(m.value())
m.set_value(decoded_value)
if m.key() is not None:
decoded_key = self._serializer.decode_message(m.key())
m.set_key(decoded_key)
messages_out.append(m)
#print(len(message))
return messages_out
But after that we run test and this method give no any performance increasing. So looks like it just for better usability. Or I need to make some additional work about serializing not single message, but whole batch.
Background:
I would like to integrate yowsup to my home automation project. I have seen a simple sample on how to receive messages and after some minor changes it is working fine.
Issue:
My problem starts when it comes to integrate the send message feature. Those are the two files I am using:
run.py
from layer import EchoLayer
from yowsup.layers.auth import YowAuthenticationProtocolLayer
from yowsup.layers.protocol_messages import YowMessagesProtocolLayer
from yowsup.layers.protocol_receipts import YowReceiptProtocolLayer
from yowsup.layers.protocol_acks import YowAckProtocolLayer
from yowsup.layers.protocol_presence import YowPresenceProtocolLayer
from yowsup.layers.network import YowNetworkLayer
from yowsup.layers.coder import YowCoderLayer
from yowsup.common import YowConstants
from yowsup.layers import YowLayerEvent
from yowsup.stacks import YowStack, YOWSUP_CORE_LAYERS
from yowsup import env
CREDENTIALS = ("phone", "pwd")
if __name__ == "__main__":
layers = (
EchoLayer,
(YowAuthenticationProtocolLayer, YowMessagesProtocolLayer, YowReceiptProtocolLayer, YowAckProtocolLayer, YowPresenceProtocolLayer)
) + YOWSUP_CORE_LAYERS
stack = YowStack(layers)
# Setting credentials
stack.setProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS, CREDENTIALS)
# WhatsApp server address
stack.setProp(YowNetworkLayer.PROP_ENDPOINT, YowConstants.ENDPOINTS[0])
stack.setProp(YowCoderLayer.PROP_DOMAIN, YowConstants.DOMAIN)
stack.setProp(YowCoderLayer.PROP_RESOURCE, env.CURRENT_ENV.getResource())
# Sending connecting signal
stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT))
# Program main loop
stack.loop()
layer.py
from yowsup.layers.interface import YowInterfaceLayer, ProtocolEntityCallback
from yowsup.layers.protocol_messages.protocolentities import TextMessageProtocolEntity
from yowsup.layers.protocol_receipts.protocolentities import OutgoingReceiptProtocolEntity
from yowsup.layers.protocol_acks.protocolentities import OutgoingAckProtocolEntity
from yowsup.layers.protocol_presence.protocolentities import PresenceProtocolEntity
import threading
import logging
logger = logging.getLogger(__name__)
class EchoLayer(YowInterfaceLayer):
#ProtocolEntityCallback("message")
def onMessage(self, messageProtocolEntity):
#send receipt otherwise we keep receiving the same message over and over
print str(messageProtocolEntity.getFrom()) + ' - ' + str(messageProtocolEntity.getBody())
receipt = OutgoingReceiptProtocolEntity(messageProtocolEntity.getId(), messageProtocolEntity.getFrom())
self.toLower(receipt)
#ProtocolEntityCallback("send_message")
def sendMessage(self, destination, message, messageProtocolEntity):
outgoingMessageProtocolEntity = TextMessageProtocolEntity(
message,
to = destination + "#s.whatsapp.net")
self.toLower(outgoingMessageProtocolEntity)
#ProtocolEntityCallback("receipt")
def onReceipt(self, entity):
ack = OutgoingAckProtocolEntity(entity.getId(), "receipt", "delivery")
self.toLower(ack)
# List of (jid, message) tuples
PROP_MESSAGES = "org.openwhatsapp.yowsup.prop.sendclient.queue"
def __init__(self):
super(EchoLayer, self).__init__()
self.ackQueue = []
self.lock = threading.Condition()
#ProtocolEntityCallback("success")
def onSuccess(self, successProtocolEntity):
self.lock.acquire()
for target in self.getProp(self.__class__.PROP_MESSAGES, []):
phone, message = target
if '#' in phone:
messageEntity = TextMessageProtocolEntity(message, to = phone)
elif '-' in phone:
messageEntity = TextMessageProtocolEntity(message, to = "%s#g.us" % phone)
else:
messageEntity = TextMessageProtocolEntity(message, to = "%s#s.whatsapp.net" % phone)
self.ackQueue.append(messageEntity.getId())
self.toLower(messageEntity)
self.lock.release()
#ProtocolEntityCallback("ack")
def onAck(self, entity):
self.lock.acquire()
if entity.getId() in self.ackQueue:
self.ackQueue.pop(self.ackQueue.index(entity.getId()))
if not len(self.ackQueue):
logger.info("Message sent")
#raise KeyboardInterrupt()
self.lock.release()
Questions:
Where am I supposed to call the send_message method, so I can send messages wherever I need it?
Is there a regular event (triggering every second or something) which I could use to send my messages?
#ProtocolEntityCallback("send_message")
def sendMessage(self, destination, message, messageProtocolEntity):
outgoingMessageProtocolEntity = TextMessageProtocolEntity(
message,
to = destination + "#s.whatsapp.net")
self.toLower(outgoingMessageProtocolEntity)
In the avove code sendMessage to be called, protocolEntity.getTag() == "send_message" has to be True. You don't need it to send message.
layer.py
from yowsup.layers.interface import YowInterfaceLayer, ProtocolEntityCallback
from yowsup.layers.protocol_messages.protocolentities import TextMessageProtocolEntity
from yowsup.layers.protocol_receipts.protocolentities import OutgoingReceiptProtocolEntity
from yowsup.layers.protocol_acks.protocolentities import OutgoingAckProtocolEntity
from yowsup.layers.protocol_presence.protocolentities import PresenceProtocolEntity
import threading
import logging
logger = logging.getLogger(__name__)
recv_msg = []
class EchoLayer(YowInterfaceLayer):
def __init__(self):
super(EchoLayer, self).__init__()
self.ackQueue = []
self.lock = threading.Condition()
#ProtocolEntityCallback("message")
def onMessage(self, messageProtocolEntity):
if messageProtocolEntity.getType() == 'text':
recv_msg.append((messageProtocolEntity.getFrom(),messageProtocolEntity.getBody()))
#send receipt otherwise we keep receiving the same message over and over
receipt = OutgoingReceiptProtocolEntity(messageProtocolEntity.getId(), messageProtocolEntity.getFrom())
self.toLower(receipt)
#ProtocolEntityCallback("receipt")
def onReceipt(self, entity):
ack = OutgoingAckProtocolEntity(entity.getId(), "receipt", "delivery")
self.toLower(ack)
# List of (jid, message) tuples
PROP_MESSAGES = "org.openwhatsapp.yowsup.prop.sendclient.queue"
#ProtocolEntityCallback("success")
def onSuccess(self, successProtocolEntity):
self.lock.acquire()
for target in self.getProp(self.__class__.PROP_MESSAGES, []):
phone, message = target
if '#' in phone:
messageEntity = TextMessageProtocolEntity(message, to = phone)
elif '-' in phone:
messageEntity = TextMessageProtocolEntity(message, to = "%s#g.us" % phone)
else:
messageEntity = TextMessageProtocolEntity(message, to = "%s#s.whatsapp.net" % phone)
self.ackQueue.append(messageEntity.getId())
self.toLower(messageEntity)
self.lock.release()
#ProtocolEntityCallback("ack")
def onAck(self, entity):
self.lock.acquire()
if entity.getId() in self.ackQueue:
self.ackQueue.pop(self.ackQueue.index(entity.getId()))
if not len(self.ackQueue):
self.lock.release()
logger.info("Message sent")
raise KeyboardInterrupt()
self.lock.release()
To send message define a function send_message in the stack run.py. You can also import run.py and use it's function from other script.
from layer import EchoLayer, recv_msg
CREDENTIALS = ("phone", "pwd")
def send_message(destination, message):
'''
destination is <phone number> without '+'
and with country code of type string,
message is string
e.g send_message('11133434343','hello')
'''
messages = [(destination, message)]
layers = (EchoLayer,
(YowAuthenticationProtocolLayer,
YowMessagesProtocolLayer,
YowReceiptProtocolLayer,
YowAckProtocolLayer,
YowPresenceProtocolLayer)
) + YOWSUP_CORE_LAYERS
stack = YowStack(layers)
stack.setProp(EchoLayer.PROP_MESSAGES, messages)
stack.setProp(YowAuthenticationProtocolLayer.PROP_PASSIVE, True)
# Setting credentials
stack.setProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS, CREDENTIALS)
# WhatsApp server address
stack.setProp(YowNetworkLayer.PROP_ENDPOINT, YowConstants.ENDPOINTS[0])
stack.setProp(YowCoderLayer.PROP_DOMAIN, YowConstants.DOMAIN)
stack.setProp(YowCoderLayer.PROP_RESOURCE, env.CURRENT_ENV.getResource())
# Sending connecting signal
stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT))
try:
# Program main loop
stack.loop()
except AuthError as e:
print('Authentication error %s' % e.message)
sys.exit(1)
def recv_message():
layers = ( EchoLayer,
(YowAuthenticationProtocolLayer, YowMessagesProtocolLayer,
YowReceiptProtocolLayer, YowAckProtocolLayer,
YowPresenceProtocolLayer)
) + YOWSUP_CORE_LAYERS
stack = YowStack(layers)
# Setting credentials
stack.setProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS, CREDENTIALS)
# WhatsApp server address
stack.setProp(YowNetworkLayer.PROP_ENDPOINT, YowConstants.ENDPOINTS[0])
stack.setProp(YowCoderLayer.PROP_DOMAIN, YowConstants.DOMAIN)
stack.setProp(YowCoderLayer.PROP_RESOURCE, env.CURRENT_ENV.getResource())
# Sending connecting signal
stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT))
try:
# Program main loop
stack.loop()
except AuthError as e:
print('Authentication error %s' % e.message)
sys.exit(1)
if __name__ == '__main__':
if len(sys.argv) == 1:
print('%s send number message\nrecv\n' % sys.argv[0])
sys.exit(1)
if sys.argv[1] == 'send':
try:
send_message(sys.argv[2],sys.argv[3])
except KeyboardInterrupt:
print('closing')
sys.exit(0)
if sys.argv[1] == 'recv':
try:
recv_message()
except KeyboardInterrupt:
print('closing')
sys.exit(0)
for m in recv_msg:
print('From %s:\n%s\n' % m)
Now you can send message by calling send_message('1234567890','Howdy') and recieve message by calling recv_message().