8000 Add Azure Queue Storage (#1543) · yosshy/botbuilder-python@fe6e900 · GitHub
[go: up one dir, main page]

Skip to content

Commit fe6e900

Browse files
mdrichardsonMichael Richardsonaxelsrz
authored
Add Azure Queue Storage (microsoft#1543)
* add queue_storage * disable test w/o emulator * black compliance * pylint compliance Co-authored-by: Michael Richardson <v-micric@microsoft.com> Co-authored-by: Axel Suárez <axsuarez@microsoft.com>
1 parent 9567078 commit fe6e900

File tree

7 files changed

+159
-0
lines changed

7 files changed

+159
-0
lines changed

libraries/botbuilder-azure/botbuilder/azure/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# --------------------------------------------------------------------------
77

88
from .about import __version__
9+
from .azure_queue_storage import AzureQueueStorage
910
from .cosmosdb_storage import CosmosDbStorage, CosmosDbConfig, CosmosDbKeyEscape
1011
from .cosmosdb_partitioned_storage import (
1112
CosmosDbPartitionedStorage,
@@ -14,6 +15,7 @@
1415
from .blob_storage import BlobStorage, BlobStorageSettings
1516

1617
__all__ = [
18+
"AzureQueueStorage",
1719
"BlobStorage",
1820
"BlobStorageSettings",
1921
"CosmosDbStorage",
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from azure.core.exceptions import ResourceExistsError
5+
from azure.storage.queue.aio import QueueClient
6+
from jsonpickle import encode
7+
8+
from botbuilder.core import QueueStorage
9+
from botbuilder.schema import Activity
10+
11+
12+
class AzureQueueStorage(QueueStorage):
13+
def __init__(self, queues_storage_connection_string: str, queue_name: str):
14+
if not queues_storage_connection_string:
15+
raise Exception("queues_storage_connection_string cannot be empty.")
16+
if not queue_name:
17+
raise Exception("queue_name cannot be empty.")
18+
19+
self.__queue_client = QueueClient.from_connection_string(
20+
queues_storage_connection_string, queue_name
21+
)
22+
23+
self.__initialized = False
24+
25+
async def _initialize(self):
26+
if self.__initialized is False:
27+
# This should only happen once - assuming this is a singleton.
28+
# There is no `create_queue_if_exists` or `exists` method, so we need to catch the ResourceExistsError.
29+
try:
30+
await self.__queue_client.create_queue()
31+
except ResourceExistsError:
32+
pass
33+
self.__initialized = True
34+
return self.__initialized
35+
36+
async def queue_activity(
37+
self,
38+
activity: Activity,
39+
visibility_timeout: int = None,
40+
time_to_live: int = None,
41+
) -> str:
42+
"""
43+
Enqueues an Activity for later processing. The visibility timeout specifies how long the message should be
44+
visible to Dequeue and Peek operations.
45+
46+
:param activity: The activity to be queued for later processing.
47+
:type activity: :class:`botbuilder.schema.Activity`
48+
:param visibility_timeout: Visibility timeout in seconds. Optional with a default value of 0.
49+
Cannot be larger than 7 days.
50+
:type visibility_timeout: int
51+
:param time_to_live: Specifies the time-to-live interval for the message in seconds.
52+
:type time_to_live: int
53+
54+
:returns: QueueMessage as a JSON string.
55+
:rtype: :class:`azure.storage.queue.QueueMessage`
56+
"""
57+
await self._initialize()
58+
59+
# Encode the activity as a JSON string.
60+
message = encode(activity)
61+
62+
receipt = await self.__queue_client.send_message(
63+
message, visibility_timeout=visibility_timeout, time_to_live=time_to_live
64+
)
65+
66+
# Encode the QueueMessage receipt as a JSON string.
67+
return encode(receipt)

libraries/botbuilder-azure/botbuilder/azure/blob_storage.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
14
import json
25
from typing import Dict, List
36

libraries/botbuilder-azure/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
REQUIRES = [
88
"azure-cosmos==3.2.0",
99
"azure-storage-blob==12.7.0",
10+
"azure-storage-queue==12.1.5",
1011
"botbuilder-schema==4.13.0",
1112
"botframework-connector==4.13.0",
1213
"jsonpickle>=1.2,<1.5",
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import unittest
5+
import aiounittest
6+
from jsonpickle import decode
7+
8+
from botbuilder.azure import AzureQueueStorage
9+
10+
EMULATOR_RUNNING = False
11+
12+
# This connection string is to connect to local Azure Storage Emulator.
13+
CONNECTION_STRING = (
14+
"AccountName=devstoreaccount1;"
15+
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr"
16+
"/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;"
17+
"BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
18+
"QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;"
19+
"TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;"
20+
)
21+
QUEUE_NAME = "queue"
22+
23+
24+
class TestAzureQueueStorageConstructor:
25+
def test_queue_storage_init_should_error_without_connection_string(self):
26+
try:
27+
# pylint: disable=no-value-for-parameter
28+
AzureQueueStorage()
29+
except Exception as error:
30+
assert error
31+
32+
def test_queue_storage_init_should_error_without_queue_name(self):
33+
try:
34+
# pylint: disable=no-value-for-parameter
35+
AzureQueueStorage(queues_storage_connection_string="somestring")
36+
except Exception as error:
37+
assert error
38+
39+
40+
class TestAzureQueueStorage(aiounittest.AsyncTestCase):
41+
@unittest.skipIf(not EMULATOR_RUNNING, reason="Needs the emulator to run.")
42+
async def test_returns_read_receipt(self):
43+
message = {"string": "test", "object": {"string2": "test2"}, "number": 99}
44+
queue = AzureQueueStorage(CONNECTION_STRING, QUEUE_NAME)
45+
46+
receipt = await queue.queue_activity(message)
47+
decoded = decode(receipt)
48+
49+
assert decoded.id is not None
50+
assert decode(decoded.content) == message

libraries/botbuilder-core/botbuilder/core/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from .middleware_set import AnonymousReceiveMiddleware, Middleware, MiddlewareSet
3131
from .null_telemetry_client import NullTelemetryClient
3232
from .private_conversation_state import PrivateConversationState
33+
from .queue_storage import QueueStorage
3334
from .recognizer import Recognizer
3435
from .recognizer_result import RecognizerResult, TopIntent
3536
from .show_typing_middleware import ShowTypingMiddleware
@@ -77,6 +78,7 @@
7778
"MiddlewareSet",
7879
"NullTelemetryClient",
7980
"PrivateConversationState",
81+
"QueueStorage",
8082
"RegisterClassMiddleware",
8183
"Recognizer",
8284
"RecognizerResult",
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from abc import ABC, abstractmethod
5+
from botbuilder.schema import Activity
6+
7+
8+
class QueueStorage(ABC):
9+
"""
10+
A base class for enqueueing an Activity for later processing.
11+
"""
12+
13+
@abstractmethod
14+
async def queue_activity(
15+
self,
16+
activity: Activity,
17+
visibility_timeout: int = None,
18+
time_to_live: int = None,
19+
) -> str:
20+
"""
21+
Enqueues an Activity for later processing. The visibility timeout specifies how long the message should be
22+
visible to Dequeue and Peek operations.
23+
24+
:param activity: The activity to be queued for later processing.
25+
:type activity: :class:`botbuilder.schema.Activity`
26+
:param visibility_timeout: Visibility timeout in seconds. Optional with a default value of 0.
27+
Cannot be larger than 7 days.
28+
:type visibility_timeout: int
29+
:param time_to_live: Specifies the time-to-live interval for the message in seconds.
30+
:type time_to_live: int
31+
32+
:returns: String representing the read receipt.
33+
"""
34+
raise NotImplementedError()

0 commit comments

Comments
 (0)
0