diff --git a/samples/django/13.core-bot/README-LUIS.md b/samples/django/13.core-bot/README-LUIS.md
new file mode 100644
index 000000000..b6b9b925f
--- /dev/null
+++ b/samples/django/13.core-bot/README-LUIS.md
@@ -0,0 +1,216 @@
+# Setting up LUIS via CLI:
+
+This README contains information on how to create and deploy a LUIS application. When the bot is ready to be deployed to production, we recommend creating a LUIS Endpoint Resource for usage with your LUIS App.
+
+> _For instructions on how to create a LUIS Application via the LUIS portal, see these Quickstart steps:_
+> 1. _[Quickstart: Create a new app in the LUIS portal][Quickstart-create]_
+> 2. _[Quickstart: Deploy an app in the LUIS portal][Quickstart-deploy]_
+
+ [Quickstart-create]: https://docs.microsoft.com/azure/cognitive-services/luis/get-started-portal-build-app
+ [Quickstart-deploy]:https://docs.microsoft.com/azure/cognitive-services/luis/get-started-portal-deploy-app
+
+## Table of Contents:
+
+- [Prerequisites](#Prerequisites)
+- [Import a new LUIS Application using a local LUIS application](#Import-a-new-LUIS-Application-using-a-local-LUIS-application)
+- [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#How-to-create-a-LUIS-Endpoint-resource-in-Azure-and-pair-it-with-a-LUIS-Application)
+
+___
+
+## [Prerequisites](#Table-of-Contents):
+
+#### Install Azure CLI >=2.0.61:
+
+Visit the following page to find the correct installer for your OS:
+- https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest
+
+#### Install LUIS CLI >=2.4.0:
+
+Open a CLI of your choice and type the following:
+
+```bash
+npm i -g luis-apis@^2.4.0
+```
+
+#### LUIS portal account:
+
+You should already have a LUIS account with either https://luis.ai, https://eu.luis.ai, or https://au.luis.ai. To determine where to create a LUIS account, consider where you will deploy your LUIS applications, and then place them in [the corresponding region][LUIS-Authoring-Regions].
+
+After you've created your account, you need your [Authoring Key][LUIS-AKey] and a LUIS application ID.
+
+ [LUIS-Authoring-Regions]: https://docs.microsoft.com/azure/cognitive-services/luis/luis-reference-regions#luis-authoring-regions]
+ [LUIS-AKey]: https://docs.microsoft.com/azure/cognitive-services/luis/luis-concept-keys#authoring-key
+
+___
+
+## [Import a new LUIS Application using a local LUIS application](#Table-of-Contents)
+
+### 1. Import the local LUIS application to luis.ai
+
+```bash
+luis import application --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appName "FlightBooking" --in "./cognitiveModels/FlightBooking.json"
+```
+
+Outputs the following JSON:
+
+```json
+{
+ "id": "########-####-####-####-############",
+ "name": "FlightBooking",
+ "description": "A LUIS model that uses intent and entities.",
+ "culture": "en-us",
+ "usageScenario": "",
+ "domain": "",
+ "versionsCount": 1,
+ "createdDateTime": "2019-03-29T18:32:02Z",
+ "endpoints": {},
+ "endpointHitsCount": 0,
+ "activeVersion": "0.1",
+ "ownerEmail": "bot@contoso.com",
+ "tokenizerVersion": "1.0.0"
+}
+```
+
+For the next step, you'll need the `"id"` value for `--appId` and the `"activeVersion"` value for `--versionId`.
+
+### 2. Train the LUIS Application
+
+```bash
+luis train version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --wait
+```
+
+### 3. Publish the LUIS Application
+
+```bash
+luis publish version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --publishRegion "LuisAppPublishRegion"
+```
+
+> `--region` corresponds to the region you _author_ your application in. The regions available for this are "westus", "westeurope" and "australiaeast".
+> These regions correspond to the three available portals, https://luis.ai, https://eu.luis.ai, or https://au.luis.ai.
+> `--publishRegion` corresponds to the region of the endpoint you're publishing to, (e.g. "westus", "southeastasia", "westeurope", "brazilsouth").
+> See the [reference docs][Endpoint-API] for a list of available publish/endpoint regions.
+
+ [Endpoint-API]: https://westus.dev.cognitive.microsoft.com/docs/services/5819c76f40a6350ce09de1ac/operations/5819c77140a63516d81aee78
+
+Outputs the following:
+
+```json
+ {
+ "versionId": "0.1",
+ "isStaging": false,
+ "endpointUrl": "https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/########-####-####-####-############",
+ "region": "westus",
+ "assignedEndpointKey": null,
+ "endpointRegion": "westus",
+ "failedRegions": "",
+ "publishedDateTime": "2019-03-29T18:40:32Z",
+ "directVersionPublish": false
+}
+```
+
+To see how to create an LUIS Cognitive Service Resource in Azure, please see [the next README][README-LUIS]. This Resource should be used when you want to move your bot to production. The instructions will show you how to create and pair the resource with a LUIS Application.
+
+ [README-LUIS]: ./README-LUIS.md
+
+___
+
+## [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#Table-of-Contents)
+
+### 1. Create a new LUIS Cognitive Services resource on Azure via Azure CLI
+
+> _Note:_
+> _If you don't have a Resource Group in your Azure subscription, you can create one through the Azure portal or through using:_
+> ```bash
+> az group create --subscription "AzureSubscriptionGuid" --location "westus" --name "ResourceGroupName"
+> ```
+> _To see a list of valid locations, use `az account list-locations`_
+
+
+```bash
+# Use Azure CLI to create the LUIS Key resource on Azure
+az cognitiveservices account create --kind "luis" --name "NewLuisResourceName" --sku "S0" --location "westus" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName"
+```
+
+The command will output a response similar to the JSON below:
+
+```json
+{
+ "endpoint": "https://westus.api.cognitive.microsoft.com/luis/v2.0",
+ "etag": "\"########-####-####-####-############\"",
+ "id": "/subscriptions/########-####-####-####-############/resourceGroups/ResourceGroupName/providers/Microsoft.CognitiveServices/accounts/NewLuisResourceName",
+ "internalId": "################################",
+ "kind": "luis",
+ "location": "westus",
+ "name": "NewLuisResourceName",
+ "provisioningState": "Succeeded",
+ "resourceGroup": "ResourceGroupName",
+ "sku": {
+ "name": "S0",
+ "tier": null
+ },
+ "tags": null,
+ "type": "Microsoft.CognitiveServices/accounts"
+}
+```
+
+
+
+Take the output from the previous command and create a JSON file in the following format:
+
+```json
+{
+ "azureSubscriptionId": "00000000-0000-0000-0000-000000000000",
+ "resourceGroup": "ResourceGroupName",
+ "accountName": "NewLuisResourceName"
+}
+```
+
+### 2. Retrieve ARM access token via Azure CLI
+
+```bash
+az account get-access-token --subscription "AzureSubscriptionGuid"
+```
+
+This will return an object that looks like this:
+
+```json
+{
+ "accessToken": "eyJ0eXAiOiJKVtokentokentokentokentokeng1dCI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyIsItokenI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDcvIiwiaWF0IjoxNTUzODc3MTUwLCJuYmYiOjE1NTM4NzcxNTAsImV4cCI6MTU1Mzg4MTA1MCwiX2NsYWltX25hbWVzIjp7Imdyb3VwcyI6InNyYzEifSwiX2NsYWltX3NvdXJjZXMiOnsic3JjMSI6eyJlbmRwb2ludCI6Imh0dHBzOi8vZ3JhcGgud2luZG93cy5uZXQvNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3L3VzZXJzL2ZmZTQyM2RkLWJhM2YtNDg0Ny04NjgyLWExNTI5MDA4MjM4Ny9nZXRNZW1iZXJPYmplY3RzIn19LCJhY3IiOiIxIiwiYWlvIjoiQVZRQXEvOEtBQUFBeGVUc201NDlhVHg4RE1mMFlRVnhGZmxxOE9RSC9PODR3QktuSmRqV1FqTkkwbmxLYzB0bHJEZzMyMFZ5bWZGaVVBSFBvNUFFUTNHL0FZNDRjdk01T3M0SEt0OVJkcE5JZW9WU0dzd0kvSkk9IiwiYW1yIjpbIndpYSIsIm1mYSJdLCJhcHBpZCI6IjA0YjA3Nzk1LThkZGItNDYxYS1iYmVlLTAyZjllMWJmN2I0NiIsImFwcGlkYWNyIjoiMCIsImRldmljZWlkIjoiNDhmNDVjNjEtMTg3Zi00MjUxLTlmZWItMTllZGFkZmMwMmE3IiwiZmFtaWx5X25hbWUiOiJHdW0iLCJnaXZlbl9uYW1lIjoiU3RldmVuIiwiaXBhZGRyIjoiMTY3LjIyMC4yLjU1IiwibmFtZSI6IlN0ZXZlbiBHdW0iLCJvaWQiOiJmZmU0MjNkZC1iYTNmLTQ4NDctODY4Mi1hMTUyOTAwODIzODciLCJvbnByZW1fc2lkIjoiUy0xLTUtMjEtMjEyNzUyMTE4NC0xNjA0MDEyOTIwLTE4ODc5Mjc1MjctMjYwOTgyODUiLCJwdWlkIjoiMTAwMzdGRkVBMDQ4NjlBNyIsInJoIjoiSSIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6Ik1rMGRNMWszN0U5ckJyMjhieUhZYjZLSU85LXVFQVVkZFVhNWpkSUd1Nk0iLCJ0aWQiOiI3MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDciLCJ1bmlxdWVfbmFtZSI6InN0Z3VtQG1pY3Jvc29mdC5jb20iLCJ1cG4iOiJzdGd1bUBtaWNyb3NvZnQuY29tIiwidXRpIjoiT2w2NGN0TXY4RVNEQzZZQWRqRUFtokenInZlciI6IjEuMCJ9.kFAsEilE0mlS1pcpqxf4rEnRKeYsehyk-gz-zJHUrE__oad3QjgDSBDPrR_ikLdweynxbj86pgG4QFaHURNCeE6SzrbaIrNKw-n9jrEtokenlosOxg_0l2g1LeEUOi5Q4gQREAU_zvSbl-RY6sAadpOgNHtGvz3Rc6FZRITfkckSLmsKAOFoh-aWC6tFKG8P52rtB0qVVRz9tovBeNqkMYL49s9ypduygbXNVwSQhm5JszeWDgrFuVFHBUP_iENCQYGQpEZf_KvjmX1Ur1F9Eh9nb4yI2gFlKncKNsQl-tokenK7-tokentokentokentokentokentokenatoken",
+ "expiresOn": "2200-12-31 23:59:59.999999",
+ "subscription": "AzureSubscriptionGuid",
+ "tenant": "tenant-guid",
+ "tokenType": "Bearer"
+}
+```
+
+The value needed for the next step is the `"accessToken"`.
+
+### 3. Use `luis add appazureaccount` to pair your LUIS resource with a LUIS Application
+
+```bash
+luis add appazureaccount --in "path/to/created/requestBody.json" --appId "LuisAppId" --authoringKey "LuisAuthoringKey" --armToken "accessToken"
+```
+
+If successful, it should yield a response like this:
+
+```json
+{
+ "code": "Success",
+ "message": "Operation Successful"
+}
+```
+
+### 4. See the LUIS Cognitive Services' keys
+
+```bash
+az cognitiveservices account keys list --name "NewLuisResourceName" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName"
+```
+
+This will return an object that looks like this:
+
+```json
+{
+ "key1": "9a69####dc8f####8eb4####399f####",
+ "key2": "####f99e####4b1a####fb3b####6b9f"
+}
+```
\ No newline at end of file
diff --git a/samples/django/13.core-bot/README.md b/samples/django/13.core-bot/README.md
new file mode 100644
index 000000000..1724d8d04
--- /dev/null
+++ b/samples/django/13.core-bot/README.md
@@ -0,0 +1,61 @@
+# CoreBot
+
+Bot Framework v4 core bot sample.
+
+This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to:
+
+- Use [LUIS](https://www.luis.ai) to implement core AI capabilities
+- Implement a multi-turn conversation using Dialogs
+- Handle user interruptions for such things as `Help` or `Cancel`
+- Prompt for and validate requests for information from the user
+
+## Prerequisites
+
+This sample **requires** prerequisites in order to run.
+
+### Overview
+
+This bot uses [LUIS](https://www.luis.ai), an AI based cognitive service, to implement language understanding.
+
+### Install Python 3.6
+
+
+### Create a LUIS Application to enable language understanding
+
+LUIS language model setup, training, and application configuration steps can be found [here](https://docs.microsoft.com/azure/bot-service/bot-builder-howto-v4-luis?view=azure-bot-service-4.0&tabs=cs).
+
+If you wish to create a LUIS application via the CLI, these steps can be found in the [README-LUIS.md](README-LUIS.md).
+
+
+### Configure your bot to use your LUIS app
+
+Update config.py with your newly imported LUIS app id, LUIS API key from https:///user/settings, LUIS API host name, ie .api.cognitive.microsoft.com. LUIS authoring region is listed on https:///user/settings.
+
+
+## Testing the bot using Bot Framework Emulator
+
+[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel.
+
+- Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases)
+
+### Connect to the bot using Bot Framework Emulator
+
+- Launch Bot Framework Emulator
+- File -> Open Bot
+- Enter a Bot URL of `http://localhost:3978/api/messages`
+
+
+## Further reading
+
+- [Bot Framework Documentation](https://docs.botframework.com)
+- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0)
+- [Dialogs](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0)
+- [Gathering Input Using Prompts](https://docs.microsoft.com/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp)
+- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0)
+- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0)
+- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0)
+- [.NET Core CLI tools](https://docs.microsoft.com/dotnet/core/tools/?tabs=netcore2x)
+- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest)
+- [Azure Portal](https://portal.azure.com)
+- [Language Understanding using LUIS](https://docs.microsoft.com/azure/cognitive-services/luis/)
+- [Channels and Bot Connector Service](https://docs.microsoft.com/azure/bot-service/bot-concepts?view=azure-bot-service-4.0)
\ No newline at end of file
diff --git a/samples/django/13.core-bot/booking_details.py b/samples/django/13.core-bot/booking_details.py
new file mode 100644
index 000000000..dbee56240
--- /dev/null
+++ b/samples/django/13.core-bot/booking_details.py
@@ -0,0 +1,11 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+"""Booking detail."""
+class BookingDetails:
+ """Booking detail implementation"""
+ def __init__(self, destination: str = None, origin: str = None, travel_date: str = None):
+ self.destination = destination
+ self.origin = origin
+ self.travel_date = travel_date
+
\ No newline at end of file
diff --git a/samples/django/13.core-bot/bots/__init__.py b/samples/django/13.core-bot/bots/__init__.py
new file mode 100644
index 000000000..74b723fd8
--- /dev/null
+++ b/samples/django/13.core-bot/bots/__init__.py
@@ -0,0 +1,10 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+"""bots module."""
+
+from .dialog_bot import DialogBot
+from .dialog_and_welcome_bot import DialogAndWelcomeBot
+
+__all__ = [
+ 'DialogBot',
+ 'DialogAndWelcomeBot']
diff --git a/samples/django/13.core-bot/bots/bots.py b/samples/django/13.core-bot/bots/bots.py
new file mode 100644
index 000000000..b68118735
--- /dev/null
+++ b/samples/django/13.core-bot/bots/bots.py
@@ -0,0 +1,43 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+""" Bot initialization """
+# pylint: disable=line-too-long
+import sys
+from django.apps import AppConfig
+from botbuilder.core import (BotFrameworkAdapter, BotFrameworkAdapterSettings, TurnContext, ConversationState, MemoryStorage, UserState)
+from dialogs import MainDialog
+from bots import DialogAndWelcomeBot
+import config
+
+class BotConfig(AppConfig):
+ """ Bot initialization """
+ name = 'bots'
+ appConfig = config.DefaultConfig
+
+ SETTINGS = BotFrameworkAdapterSettings(appConfig.APP_ID, appConfig.APP_PASSWORD)
+ ADAPTER = BotFrameworkAdapter(SETTINGS)
+
+ # Create MemoryStorage, UserState and ConversationState
+ memory = MemoryStorage()
+ user_state = UserState(memory)
+ conversation_state = ConversationState(memory)
+
+ dialog = MainDialog(appConfig)
+ bot = DialogAndWelcomeBot(conversation_state, user_state, dialog)
+
+ async def on_error(self, context: TurnContext, error: Exception):
+ """
+ Catch-all for errors.
+ This check writes out errors to console log
+ NOTE: In production environment, you should consider logging this to Azure
+ application insights.
+ """
+ print(f'\n [on_turn_error]: { error }', file=sys.stderr)
+ # Send a message to the user
+ await context.send_activity('Oops. Something went wrong!')
+ # Clear out state
+ await self.conversation_state.delete(context)
+
+ def ready(self):
+ self.ADAPTER.on_turn_error = self.on_error
diff --git a/samples/django/13.core-bot/bots/dialog_and_welcome_bot.py b/samples/django/13.core-bot/bots/dialog_and_welcome_bot.py
new file mode 100644
index 000000000..357b00fef
--- /dev/null
+++ b/samples/django/13.core-bot/bots/dialog_and_welcome_bot.py
@@ -0,0 +1,42 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+"""Main dialog to welcome users."""
+import json
+import os.path
+from typing import List
+from botbuilder.core import TurnContext
+from botbuilder.schema import Activity, Attachment, ChannelAccount
+from helpers.activity_helper import create_activity_reply
+from .dialog_bot import DialogBot
+
+class DialogAndWelcomeBot(DialogBot):
+ """Main dialog to welcome users implementation."""
+
+ async def on_members_added_activity(self, members_added: List[ChannelAccount],
+ turn_context: TurnContext):
+ for member in members_added:
+ # Greet anyone that was not the target (recipient) of this message.
+ # To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards
+ # for more details.
+ if member.id != turn_context.activity.recipient.id:
+ welcome_card = self.create_adaptive_card_attachment()
+ response = self.create_response(turn_context.activity, welcome_card)
+ await turn_context.send_activity(response)
+
+ def create_response(self, activity: Activity, attachment: Attachment):
+ """Create an attachment message response."""
+ response = create_activity_reply(activity)
+ response.attachments = [attachment]
+ return response
+
+ # Load attachment from file.
+ def create_adaptive_card_attachment(self):
+ """Create an adaptive card."""
+ relative_path = os.path.abspath(os.path.dirname(__file__))
+ path = os.path.join(relative_path, "resources/welcomeCard.json")
+ with open(path) as card_file:
+ card = json.load(card_file)
+
+ return Attachment(
+ content_type="application/vnd.microsoft.card.adaptive",
+ content=card)
diff --git a/samples/django/13.core-bot/bots/dialog_bot.py b/samples/django/13.core-bot/bots/dialog_bot.py
new file mode 100644
index 000000000..f73b71c72
--- /dev/null
+++ b/samples/django/13.core-bot/bots/dialog_bot.py
@@ -0,0 +1,34 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+"""Implements bot Activity handler."""
+
+from botbuilder.core import ActivityHandler, ConversationState, UserState, TurnContext
+from botbuilder.dialogs import Dialog
+from helpers.dialog_helper import DialogHelper
+
+class DialogBot(ActivityHandler):
+ """Main activity handler for the bot."""
+ def __init__(self, conversation_state: ConversationState,
+ user_state: UserState, dialog: Dialog):
+ if conversation_state is None:
+ raise Exception('[DialogBot]: Missing parameter. conversation_state is required')
+ if user_state is None:
+ raise Exception('[DialogBot]: Missing parameter. user_state is required')
+ if dialog is None:
+ raise Exception('[DialogBot]: Missing parameter. dialog is required')
+
+ self.conversation_state = conversation_state
+ self.user_state = user_state
+ self.dialog = dialog
+ self.dialogState = self.conversation_state.create_property('DialogState') # pylint: disable=C0103
+
+ async def on_turn(self, turn_context: TurnContext):
+ await super().on_turn(turn_context)
+
+ # Save any state changes that might have occured during the turn.
+ await self.conversation_state.save_changes(turn_context, False)
+ await self.user_state.save_changes(turn_context, False)
+
+ async def on_message_activity(self, turn_context: TurnContext):
+ await DialogHelper.run_dialog(self.dialog, turn_context,
+ self.conversation_state.create_property("DialogState")) # pylint: disable=C0103
diff --git a/samples/django/13.core-bot/bots/resources/welcomeCard.json b/samples/django/13.core-bot/bots/resources/welcomeCard.json
new file mode 100644
index 000000000..b6b5f1828
--- /dev/null
+++ b/samples/django/13.core-bot/bots/resources/welcomeCard.json
@@ -0,0 +1,46 @@
+{
+ "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
+ "type": "AdaptiveCard",
+ "version": "1.0",
+ "body": [
+ {
+ "type": "Image",
+ "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU",
+ "size": "stretch"
+ },
+ {
+ "type": "TextBlock",
+ "spacing": "medium",
+ "size": "default",
+ "weight": "bolder",
+ "text": "Welcome to Bot Framework!",
+ "wrap": true,
+ "maxLines": 0
+ },
+ {
+ "type": "TextBlock",
+ "size": "default",
+ "isSubtle": "yes",
+ "text": "Now that you have successfully run your bot, follow the links in this Adaptive Card to expand your knowledge of Bot Framework.",
+ "wrap": true,
+ "maxLines": 0
+ }
+ ],
+ "actions": [
+ {
+ "type": "Action.OpenUrl",
+ "title": "Get an overview",
+ "url": "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0"
+ },
+ {
+ "type": "Action.OpenUrl",
+ "title": "Ask a question",
+ "url": "https://stackoverflow.com/questions/tagged/botframework"
+ },
+ {
+ "type": "Action.OpenUrl",
+ "title": "Learn how to deploy",
+ "url": "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/samples/django/13.core-bot/bots/settings.py b/samples/django/13.core-bot/bots/settings.py
new file mode 100644
index 000000000..ff098f9a7
--- /dev/null
+++ b/samples/django/13.core-bot/bots/settings.py
@@ -0,0 +1,124 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+"""
+Django settings for bots project.
+
+Generated by 'django-admin startproject' using Django 2.2.1.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/2.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/2.2/ref/settings/
+"""
+
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'q8)bznhagppa$5^_0v8#pm@2)j2@-wh-6waq$hhks5&jw#a7*v'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'bots.bots.BotConfig',
+]
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'bots.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'bots.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/2.2/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/2.2/howto/static-files/
+
+STATIC_URL = '/static/'
diff --git a/samples/django/13.core-bot/bots/urls.py b/samples/django/13.core-bot/bots/urls.py
new file mode 100644
index 000000000..99cf42018
--- /dev/null
+++ b/samples/django/13.core-bot/bots/urls.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+""" URL configuration for bot message handler """
+
+from django.urls import path
+from django.views.decorators.csrf import csrf_exempt
+from . import views
+
+# pylint:disable=invalid-name
+urlpatterns = [
+ path("", views.home, name="home"),
+ path("api/messages", csrf_exempt(views.messages), name="messages"),
+]
diff --git a/samples/django/13.core-bot/bots/views.py b/samples/django/13.core-bot/bots/views.py
new file mode 100644
index 000000000..5417befba
--- /dev/null
+++ b/samples/django/13.core-bot/bots/views.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+"""
+This sample shows how to create a bot that demonstrates the following:
+- Use [LUIS](https://www.luis.ai) to implement core AI capabilities.
+- Implement a multi-turn conversation using Dialogs.
+- Handle user interruptions for such things as `Help` or `Cancel`.
+- Prompt for and validate requests for information from the user.
+"""
+
+import asyncio
+import json
+from django.http import HttpResponse
+from django.apps import apps
+from botbuilder.schema import Activity
+
+# pylint: disable=line-too-long
+def home():
+ """Default handler."""
+ return HttpResponse("Hello!")
+
+def messages(request):
+ """Main bot message handler."""
+ if request.headers['Content-Type'] == 'application/json':
+ body = json.loads(request.body.decode("utf-8"))
+ else:
+ return HttpResponse(status=415)
+
+ activity = Activity().deserialize(body)
+ auth_header = request.headers['Authorization'] if 'Authorization' in request.headers else ''
+ loop = asyncio.get_event_loop()
+
+ bot_app = apps.get_app_config('bots')
+ bot = bot_app.bot
+ adapter = bot_app.ADAPTER
+
+ async def aux_func(turn_context):
+ asyncio.ensure_future(bot.on_turn(turn_context), loop=loop)
+ try:
+ task = asyncio.ensure_future(adapter.process_activity(activity, auth_header, aux_func), loop=loop)
+ loop.run_until_complete(task)
+ return HttpResponse(status=201)
+ except Exception as exception:
+ raise exception
+ return HttpResponse("This is message processing!")
diff --git a/samples/django/13.core-bot/bots/wsgi.py b/samples/django/13.core-bot/bots/wsgi.py
new file mode 100644
index 000000000..4475b3fbe
--- /dev/null
+++ b/samples/django/13.core-bot/bots/wsgi.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+"""
+WSGI config for bots project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
+"""
+
+import os
+from django.core.wsgi import get_wsgi_application
+
+# pylint:disable=invalid-name
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'bots.settings')
+application = get_wsgi_application()
diff --git a/samples/django/13.core-bot/cognitiveModels/FlightBooking.json b/samples/django/13.core-bot/cognitiveModels/FlightBooking.json
new file mode 100644
index 000000000..0a0d6c4a7
--- /dev/null
+++ b/samples/django/13.core-bot/cognitiveModels/FlightBooking.json
@@ -0,0 +1,226 @@
+{
+ "luis_schema_version": "3.2.0",
+ "versionId": "0.1",
+ "name": "Airline Reservation",
+ "desc": "A LUIS model that uses intent and entities.",
+ "culture": "en-us",
+ "tokenizerVersion": "1.0.0",
+ "intents": [
+ {
+ "name": "Book flight"
+ },
+ {
+ "name": "Cancel"
+ },
+ {
+ "name": "None"
+ }
+ ],
+ "entities": [],
+ "composites": [
+ {
+ "name": "From",
+ "children": [
+ "Airport"
+ ],
+ "roles": []
+ },
+ {
+ "name": "To",
+ "children": [
+ "Airport"
+ ],
+ "roles": []
+ }
+ ],
+ "closedLists": [
+ {
+ "name": "Airport",
+ "subLists": [
+ {
+ "canonicalForm": "Paris",
+ "list": [
+ "paris"
+ ]
+ },
+ {
+ "canonicalForm": "London",
+ "list": [
+ "london"
+ ]
+ },
+ {
+ "canonicalForm": "Berlin",
+ "list": [
+ "berlin"
+ ]
+ },
+ {
+ "canonicalForm": "New York",
+ "list": [
+ "new york"
+ ]
+ }
+ ],
+ "roles": []
+ }
+ ],
+ "patternAnyEntities": [],
+ "regex_entities": [],
+ "prebuiltEntities": [
+ {
+ "name": "datetimeV2",
+ "roles": []
+ }
+ ],
+ "model_features": [],
+ "regex_features": [],
+ "patterns": [],
+ "utterances": [
+ {
+ "text": "book flight from london to paris on feb 14th",
+ "intent": "Book flight",
+ "entities": [
+ {
+ "entity": "To",
+ "startPos": 27,
+ "endPos": 31
+ },
+ {
+ "entity": "From",
+ "startPos": 17,
+ "endPos": 22
+ }
+ ]
+ },
+ {
+ "text": "book flight to berlin on feb 14th",
+ "intent": "Book flight",
+ "entities": [
+ {
+ "entity": "To",
+ "startPos": 15,
+ "endPos": 20
+ }
+ ]
+ },
+ {
+ "text": "book me a flight from london to paris",
+ "intent": "Book flight",
+ "entities": [
+ {
+ "entity": "From",
+ "startPos": 22,
+ "endPos": 27
+ },
+ {
+ "entity": "To",
+ "startPos": 32,
+ "endPos": 36
+ }
+ ]
+ },
+ {
+ "text": "bye",
+ "intent": "Cancel",
+ "entities": []
+ },
+ {
+ "text": "cancel booking",
+ "intent": "Cancel",
+ "entities": []
+ },
+ {
+ "text": "exit",
+ "intent": "Cancel",
+ "entities": []
+ },
+ {
+ "text": "flight to paris",
+ "intent": "Book flight",
+ "entities": [
+ {
+ "entity": "To",
+ "startPos": 10,
+ "endPos": 14
+ }
+ ]
+ },
+ {
+ "text": "flight to paris from london on feb 14th",
+ "intent": "Book flight",
+ "entities": [
+ {
+ "entity": "To",
+ "startPos": 10,
+ "endPos": 14
+ },
+ {
+ "entity": "From",
+ "startPos": 21,
+ "endPos": 26
+ }
+ ]
+ },
+ {
+ "text": "fly from berlin to paris on may 5th",
+ "intent": "Book flight",
+ "entities": [
+ {
+ "entity": "To",
+ "startPos": 19,
+ "endPos": 23
+ },
+ {
+ "entity": "From",
+ "startPos": 9,
+ "endPos": 14
+ }
+ ]
+ },
+ {
+ "text": "go to paris",
+ "intent": "Book flight",
+ "entities": [
+ {
+ "entity": "To",
+ "startPos": 6,
+ "endPos": 10
+ }
+ ]
+ },
+ {
+ "text": "going from paris to berlin",
+ "intent": "Book flight",
+ "entities": [
+ {
+ "entity": "To",
+ "startPos": 20,
+ "endPos": 25
+ },
+ {
+ "entity": "From",
+ "startPos": 11,
+ "endPos": 15
+ }
+ ]
+ },
+ {
+ "text": "ignore",
+ "intent": "Cancel",
+ "entities": []
+ },
+ {
+ "text": "travel to paris",
+ "intent": "Book flight",
+ "entities": [
+ {
+ "entity": "To",
+ "startPos": 10,
+ "endPos": 14
+ }
+ ]
+ }
+ ],
+ "settings": []
+}
\ No newline at end of file
diff --git a/samples/django/13.core-bot/config.py b/samples/django/13.core-bot/config.py
new file mode 100644
index 000000000..8bf76757c
--- /dev/null
+++ b/samples/django/13.core-bot/config.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python3
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+""" Bot Configuration """
+class DefaultConfig(object):
+ """ Bot Configuration """
+ PORT = 3978
+ APP_ID = ""
+ APP_PASSWORD = ""
+
+ LUIS_APP_ID = ""
+ # LUIS authoring key from LUIS portal or LUIS Cognitive Service subscription key
+ LUIS_API_KEY = ""
+ # LUIS endpoint host name, ie "https://westus.api.cognitive.microsoft.com"
+ LUIS_API_HOST_NAME = ""
+
\ No newline at end of file
diff --git a/samples/django/13.core-bot/db.sqlite3 b/samples/django/13.core-bot/db.sqlite3
new file mode 100644
index 000000000..e69de29bb
diff --git a/samples/django/13.core-bot/dialogs/__init__.py b/samples/django/13.core-bot/dialogs/__init__.py
new file mode 100644
index 000000000..1d3c05892
--- /dev/null
+++ b/samples/django/13.core-bot/dialogs/__init__.py
@@ -0,0 +1,13 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+"""Dialogs module"""
+from .booking_dialog import BookingDialog
+from .cancel_and_help_dialog import CancelAndHelpDialog
+from .date_resolver_dialog import DateResolverDialog
+from .main_dialog import MainDialog
+
+__all__ = [
+ 'BookingDialog',
+ 'CancelAndHelpDialog',
+ 'DateResolverDialog',
+ 'MainDialog']
diff --git a/samples/django/13.core-bot/dialogs/booking_dialog.py b/samples/django/13.core-bot/dialogs/booking_dialog.py
new file mode 100644
index 000000000..95f7fba85
--- /dev/null
+++ b/samples/django/13.core-bot/dialogs/booking_dialog.py
@@ -0,0 +1,93 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+"""Flight booking dialog."""
+
+from botbuilder.dialogs import WaterfallDialog, WaterfallStepContext, DialogTurnResult
+from botbuilder.dialogs.prompts import ConfirmPrompt, TextPrompt, PromptOptions
+from botbuilder.core import MessageFactory
+from datatypes_date_time.timex import Timex
+from .cancel_and_help_dialog import CancelAndHelpDialog
+from .date_resolver_dialog import DateResolverDialog
+
+class BookingDialog(CancelAndHelpDialog):
+ """Flight booking implementation."""
+
+ def __init__(self, dialog_id: str = None):
+ super(BookingDialog, self).__init__(dialog_id or BookingDialog.__name__)
+
+ self.add_dialog(TextPrompt(TextPrompt.__name__))
+ #self.add_dialog(ConfirmPrompt(ConfirmPrompt.__name__))
+ self.add_dialog(DateResolverDialog(DateResolverDialog.__name__))
+ self.add_dialog(WaterfallDialog(WaterfallDialog.__name__, [
+ self.destination_step,
+ self.origin_step,
+ self.travel_date_step,
+ #self.confirm_step,
+ self.final_step
+ ]))
+
+ self.initial_dialog_id = WaterfallDialog.__name__
+
+ async def destination_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Prompt for destination."""
+ booking_details = step_context.options
+
+ if booking_details.destination is None:
+ return await step_context.prompt(TextPrompt.__name__,
+ PromptOptions(prompt=MessageFactory.text(
+ 'To what city would you like to travel?'))) # pylint: disable=line-too-long,bad-continuation
+ else:
+ return await step_context.next(booking_details.destination)
+
+ async def origin_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Prompt for origin city."""
+ booking_details = step_context.options
+
+ # Capture the response to the previous step's prompt
+ booking_details.destination = step_context.result
+ if booking_details.origin is None:
+ return await step_context.prompt(TextPrompt.__name__,
+ PromptOptions(prompt=MessageFactory.text('From what city will you be travelling?'))) # pylint: disable=line-too-long,bad-continuation
+ else:
+ return await step_context.next(booking_details.origin)
+
+ async def travel_date_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Prompt for travel date.
+ This will use the DATE_RESOLVER_DIALOG."""
+
+ booking_details = step_context.options
+
+ # Capture the results of the previous step
+ booking_details.origin = step_context.result
+ if (not booking_details.travel_date or self.is_ambiguous(booking_details.travel_date)):
+ return await step_context.begin_dialog(DateResolverDialog.__name__, booking_details.travel_date) # pylint: disable=line-too-long
+ else:
+ return await step_context.next(booking_details.travel_date)
+
+ async def confirm_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Confirm the information the user has provided."""
+ booking_details = step_context.options
+
+ # Capture the results of the previous step
+ booking_details.travel_date = step_context.result
+ msg = f'Please confirm, I have you traveling to: { booking_details.destination }'\
+ f' from: { booking_details.origin } on: { booking_details.travel_date}.'
+
+ # Offer a YES/NO prompt.
+ return await step_context.prompt(ConfirmPrompt.__name__,
+ PromptOptions(prompt=MessageFactory.text(msg)))
+
+ async def final_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Complete the interaction and end the dialog."""
+ if step_context.result:
+ booking_details = step_context.options
+ booking_details.travel_date = step_context.result
+
+ return await step_context.end_dialog(booking_details)
+ else:
+ return await step_context.end_dialog()
+
+ def is_ambiguous(self, timex: str) -> bool:
+ """Ensure time is correct."""
+ timex_property = Timex(timex)
+ return 'definite' not in timex_property.types
diff --git a/samples/django/13.core-bot/dialogs/cancel_and_help_dialog.py b/samples/django/13.core-bot/dialogs/cancel_and_help_dialog.py
new file mode 100644
index 000000000..0e9010ffb
--- /dev/null
+++ b/samples/django/13.core-bot/dialogs/cancel_and_help_dialog.py
@@ -0,0 +1,37 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+"""Handle cancel and help intents."""
+from botbuilder.dialogs import ComponentDialog, DialogContext, DialogTurnResult, DialogTurnStatus
+from botbuilder.schema import ActivityTypes
+
+
+class CancelAndHelpDialog(ComponentDialog):
+ """Implementation of handling cancel and help."""
+ async def on_begin_dialog(self, inner_dc: DialogContext, options: object) -> DialogTurnResult:
+ result = await self.interrupt(inner_dc)
+ if result is not None:
+ return result
+
+ return await super(CancelAndHelpDialog, self).on_begin_dialog(inner_dc, options)
+
+ async def on_continue_dialog(self, inner_dc: DialogContext) -> DialogTurnResult:
+ result = await self.interrupt(inner_dc)
+ if result is not None:
+ return result
+
+ return await super(CancelAndHelpDialog, self).on_continue_dialog(inner_dc)
+
+ async def interrupt(self, inner_dc: DialogContext) -> DialogTurnResult:
+ """Detect interruptions."""
+ if inner_dc.context.activity.type == ActivityTypes.message:
+ text = inner_dc.context.activity.text.lower()
+
+ if text == 'help' or text == '?':
+ await inner_dc.context.send_activity("Show Help...")
+ return DialogTurnResult(DialogTurnStatus.Waiting)
+
+ if text == 'cancel' or text == 'quit':
+ await inner_dc.context.send_activity("Cancelling")
+ return await inner_dc.cancel_all_dialogs()
+
+ return None
diff --git a/samples/django/13.core-bot/dialogs/date_resolver_dialog.py b/samples/django/13.core-bot/dialogs/date_resolver_dialog.py
new file mode 100644
index 000000000..ba910c7d2
--- /dev/null
+++ b/samples/django/13.core-bot/dialogs/date_resolver_dialog.py
@@ -0,0 +1,63 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+"""Handle date/time resolution for booking dialog."""
+from botbuilder.core import MessageFactory
+from botbuilder.dialogs import WaterfallDialog, DialogTurnResult, WaterfallStepContext
+from botbuilder.dialogs.prompts import DateTimePrompt, PromptValidatorContext, \
+ PromptOptions, DateTimeResolution
+from datatypes_date_time.timex import Timex
+from .cancel_and_help_dialog import CancelAndHelpDialog
+
+class DateResolverDialog(CancelAndHelpDialog):
+ """Resolve the date"""
+ def __init__(self, dialog_id: str = None):
+ super(DateResolverDialog, self).__init__(dialog_id or DateResolverDialog.__name__)
+
+ self.add_dialog(DateTimePrompt(DateTimePrompt.__name__,
+ DateResolverDialog.datetime_prompt_validator))
+ self.add_dialog(WaterfallDialog(WaterfallDialog.__name__ + '2', [
+ self.initial_step,
+ self.final_step
+ ]))
+
+ self.initial_dialog_id = WaterfallDialog.__name__ + '2'
+
+ async def initial_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Prompt for the date."""
+ timex = step_context.options
+
+ prompt_msg = 'On what date would you like to travel?'
+ reprompt_msg = "I'm sorry, for best results, please enter your travel "\
+ "date including the month, day and year."
+
+ if timex is None:
+ # We were not given any date at all so prompt the user.
+ return await step_context.prompt(DateTimePrompt.__name__,
+ PromptOptions( # pylint: disable=bad-continuation
+ prompt=MessageFactory.text(prompt_msg),
+ retry_prompt=MessageFactory.text(reprompt_msg)
+ ))
+ else:
+ # We have a Date we just need to check it is unambiguous.
+ if 'definite' in Timex(timex).types:
+ # This is essentially a "reprompt" of the data we were given up front.
+ return await step_context.prompt(DateTimePrompt.__name__,
+ PromptOptions(prompt=reprompt_msg))
+ else:
+ return await step_context.next(DateTimeResolution(timex=timex))
+
+ async def final_step(self, step_context: WaterfallStepContext):
+ """Cleanup - set final return value and end dialog."""
+ timex = step_context.result[0].timex
+ return await step_context.end_dialog(timex)
+
+ @staticmethod
+ async def datetime_prompt_validator(prompt_context: PromptValidatorContext) -> bool:
+ """ Validate the date provided is in proper form. """
+ if prompt_context.recognized.succeeded:
+ timex = prompt_context.recognized.value[0].timex.split('T')[0]
+
+ # TODO: Needs TimexProperty
+ return 'definite' in Timex(timex).types
+
+ return False
diff --git a/samples/django/13.core-bot/dialogs/main_dialog.py b/samples/django/13.core-bot/dialogs/main_dialog.py
new file mode 100644
index 000000000..f28d9993c
--- /dev/null
+++ b/samples/django/13.core-bot/dialogs/main_dialog.py
@@ -0,0 +1,69 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+"""Main dialog. """
+from botbuilder.dialogs import ComponentDialog, WaterfallDialog, \
+ WaterfallStepContext, DialogTurnResult
+from botbuilder.dialogs.prompts import TextPrompt, PromptOptions
+from botbuilder.core import MessageFactory
+from booking_details import BookingDetails
+from helpers.luis_helper import LuisHelper
+from .booking_dialog import BookingDialog
+
+class MainDialog(ComponentDialog):
+ """Main dialog. """
+ def __init__(self, configuration: dict, dialog_id: str = None):
+ super(MainDialog, self).__init__(dialog_id or MainDialog.__name__)
+
+ self._configuration = configuration
+
+ self.add_dialog(TextPrompt(TextPrompt.__name__))
+ self.add_dialog(BookingDialog())
+ self.add_dialog(WaterfallDialog('WFDialog', [
+ self.intro_step,
+ self.act_step,
+ self.final_step
+ ]))
+
+ self.initial_dialog_id = 'WFDialog'
+
+ async def intro_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Initial prompt."""
+ return await step_context.prompt(TextPrompt.__name__, PromptOptions(
+ prompt=MessageFactory.text("What can I help you with today?")))
+
+
+ async def act_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Use language understanding to gather details about booking."""
+
+
+ # In this sample we only have a single Intent we are concerned with.
+ # However, typically a scenario will have multiple different Intents
+ # each corresponding to starting a different child Dialog.
+ booking_details = await LuisHelper.execute_luis_query(self._configuration,\
+ step_context.context) if step_context.result is not None else BookingDetails()
+
+
+ # Run the BookingDialog giving it whatever details we have from the
+ # model. The dialog will prompt to find out the remaining details.
+ return await step_context.begin_dialog(BookingDialog.__name__, booking_details)
+
+ async def final_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Complete dialog.
+ At this step, with details from the user, display the completed
+ flight booking to the user.
+ """
+ # If the child dialog ("BookingDialog") was cancelled or the user failed
+ # to confirm, the Result here will be null.
+ if step_context.result is not None:
+ result = step_context.result
+
+ # Now we have all the booking details call the booking service.
+ # If the call to the booking service was successful tell the user.
+ #time_property = Timex(result.travel_date)
+ #travel_date_msg = time_property.to_natural_language(datetime.now())
+ msg = f'I have you booked to {result.destination} from'\
+ f' {result.origin} on {result.travel_date}.'
+ await step_context.context.send_activity(MessageFactory.text(msg))
+ else:
+ await step_context.context.send_activity(MessageFactory.text("Thank you."))
+ return await step_context.end_dialog()
diff --git a/samples/django/13.core-bot/helpers/__init__.py b/samples/django/13.core-bot/helpers/__init__.py
new file mode 100644
index 000000000..7ed8a466a
--- /dev/null
+++ b/samples/django/13.core-bot/helpers/__init__.py
@@ -0,0 +1,11 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+"""Helpers module."""
+from . import activity_helper, luis_helper, dialog_helper
+
+__all__ = [
+ 'activity_helper',
+ 'dialog_helper',
+ 'luis_helper']
+
\ No newline at end of file
diff --git a/samples/django/13.core-bot/helpers/activity_helper.py b/samples/django/13.core-bot/helpers/activity_helper.py
new file mode 100644
index 000000000..334275648
--- /dev/null
+++ b/samples/django/13.core-bot/helpers/activity_helper.py
@@ -0,0 +1,27 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+"""Helper to create reply object."""
+
+from datetime import datetime
+from botbuilder.schema import Activity, ActivityTypes, ChannelAccount, ConversationAccount
+
+def create_activity_reply(activity: Activity, text: str = None, locale: str = None):
+ """Helper to create reply object."""
+ return Activity(
+ type=ActivityTypes.message,
+ timestamp=datetime.utcnow(),
+ from_property=ChannelAccount(id=getattr(activity.recipient, 'id', None),
+ name=getattr(activity.recipient, 'name', None)),
+ recipient=ChannelAccount(id=activity.from_property.id, name=activity.from_property.name),
+ reply_to_id=activity.id,
+ service_url=activity.service_url,
+ channel_id=activity.channel_id,
+ conversation=ConversationAccount(is_group=activity.conversation.is_group,
+ id=activity.conversation.id,
+ name=activity.conversation.name),
+ text=text or '',
+ locale=locale or '',
+ attachments=[],
+ entities=[]
+ )
+
\ No newline at end of file
diff --git a/samples/django/13.core-bot/helpers/dialog_helper.py b/samples/django/13.core-bot/helpers/dialog_helper.py
new file mode 100644
index 000000000..550a17b5f
--- /dev/null
+++ b/samples/django/13.core-bot/helpers/dialog_helper.py
@@ -0,0 +1,19 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+"""Utility to run dialogs."""
+from botbuilder.core import StatePropertyAccessor, TurnContext
+from botbuilder.dialogs import Dialog, DialogSet, DialogTurnStatus
+
+class DialogHelper:
+ """Dialog Helper implementation."""
+
+ @staticmethod
+ async def run_dialog(dialog: Dialog, turn_context: TurnContext, accessor: StatePropertyAccessor): # pylint: disable=line-too-long
+ """Run dialog."""
+ dialog_set = DialogSet(accessor)
+ dialog_set.add(dialog)
+
+ dialog_context = await dialog_set.create_context(turn_context)
+ results = await dialog_context.continue_dialog()
+ if results.status == DialogTurnStatus.Empty:
+ await dialog_context.begin_dialog(dialog.id)
diff --git a/samples/django/13.core-bot/helpers/luis_helper.py b/samples/django/13.core-bot/helpers/luis_helper.py
new file mode 100644
index 000000000..9bb597c20
--- /dev/null
+++ b/samples/django/13.core-bot/helpers/luis_helper.py
@@ -0,0 +1,48 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+"""Helper to call LUIS service."""
+from botbuilder.ai.luis import LuisRecognizer, LuisApplication
+from botbuilder.core import TurnContext
+
+from booking_details import BookingDetails
+
+# pylint: disable=line-too-long
+class LuisHelper:
+ """LUIS helper implementation."""
+ @staticmethod
+ async def execute_luis_query(configuration, turn_context: TurnContext) -> BookingDetails:
+ """Invoke LUIS service to perform prediction/evaluation of utterance."""
+ booking_details = BookingDetails()
+
+ # pylint:disable=broad-except
+ try:
+ luis_application = LuisApplication(
+ configuration.LUIS_APP_ID,
+ configuration.LUIS_API_KEY,
+ configuration.LUIS_API_HOST_NAME
+ )
+
+ recognizer = LuisRecognizer(luis_application)
+ recognizer_result = await recognizer.recognize(turn_context)
+
+ if recognizer_result.intents:
+ intent = sorted(recognizer_result.intents, key=recognizer_result.intents.get, reverse=True)[:1][0]
+ if intent == 'Book_flight':
+ # We need to get the result from the LUIS JSON which at every level returns an array.
+ to_entities = recognizer_result.entities.get("$instance", {}).get("To", [])
+ if to_entities:
+ booking_details.destination = to_entities[0]['text']
+ from_entities = recognizer_result.entities.get("$instance", {}).get("From", [])
+ if from_entities:
+ booking_details.origin = from_entities[0]['text']
+
+ # This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the Time part.
+ # TIMEX is a format that represents DateTime expressions that include some ambiguity. e.g. missing a Year.
+ date_entities = recognizer_result.entities.get("$instance", {}).get("datetime", [])
+ if date_entities:
+ booking_details.travel_date = None # Set when we get a timex format
+ except Exception as exception:
+ print(exception)
+
+ return booking_details
diff --git a/samples/django/13.core-bot/manage.py b/samples/django/13.core-bot/manage.py
new file mode 100644
index 000000000..cdc5a0f34
--- /dev/null
+++ b/samples/django/13.core-bot/manage.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+from django.core.management.commands.runserver import Command as runserver
+import config
+
+def main():
+ """Django's command-line utility for administrative tasks."""
+ runserver.default_port = config.DefaultConfig.PORT
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'bots.settings')
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
+ execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/samples/django/13.core-bot/requirements.txt b/samples/django/13.core-bot/requirements.txt
new file mode 100644
index 000000000..bc7fd496e
--- /dev/null
+++ b/samples/django/13.core-bot/requirements.txt
@@ -0,0 +1,9 @@
+Django>=2.2.1
+requests>=2.18.1
+botframework-connector>=4.4.0.b1
+botbuilder-schema>=4.4.0.b1
+botbuilder-core>=4.4.0.b1
+botbuilder-dialogs>=4.4.0.b1
+botbuilder-ai>=4.4.0.b1
+datatypes-date-time>=1.0.0.a1
+azure-cognitiveservices-language-luis>=0.2.0
\ No newline at end of file
diff --git a/samples/python-flask/13.core-bot/app.py b/samples/python-flask/13.core-bot/app.py
index 542219f67..c166d45fc 100644
--- a/samples/python-flask/13.core-bot/app.py
+++ b/samples/python-flask/13.core-bot/app.py
@@ -8,53 +8,35 @@
- Implement a multi-turn conversation using Dialogs.
- Handle user interruptions for such things as `Help` or `Cancel`.
- Prompt for and validate requests for information from the user.
-gi
"""
-from functools import wraps
-import json
+
import asyncio
-import sys
-from flask import Flask, jsonify, request, Response
-from botbuilder.schema import (Activity, ActivityTypes)
-from botbuilder.core import (BotFrameworkAdapter, BotFrameworkAdapterSettings, TurnContext,
- ConversationState, MemoryStorage, UserState)
+from flask import Flask, request, Response
+from botbuilder.core import (BotFrameworkAdapter, BotFrameworkAdapterSettings,
+ ConversationState, MemoryStorage, UserState)
+from botbuilder.schema import (Activity)
from dialogs import MainDialog
from bots import DialogAndWelcomeBot
-from helpers.dialog_helper import DialogHelper
-loop = asyncio.get_event_loop()
-app = Flask(__name__, instance_relative_config=True)
-app.config.from_object('config.DefaultConfig')
+LOOP = asyncio.get_event_loop()
+APP = Flask(__name__, instance_relative_config=True)
+APP.config.from_object('config.DefaultConfig')
-SETTINGS = BotFrameworkAdapterSettings(app.config['APP_ID'], app.config['APP_PASSWORD'])
+SETTINGS = BotFrameworkAdapterSettings(APP.config['APP_ID'], APP.config['APP_PASSWORD'])
ADAPTER = BotFrameworkAdapter(SETTINGS)
-# Catch-all for errors.
-async def on_error(context: TurnContext, error: Exception):
- # This check writes out errors to console log
- # NOTE: In production environment, you should consider logging this to Azure
- # application insights.
- print(f'\n [on_turn_error]: { error }', file=sys.stderr)
- # Send a message to the user
- await context.send_activity('Oops. Something went wrong!')
- # Clear out state
- await conversation_state.delete(context)
-
-ADAPTER.on_turn_error = on_error
-
# Create MemoryStorage, UserState and ConversationState
-memory = MemoryStorage()
+MEMORY = MemoryStorage()
+USER_STATE = UserState(MEMORY)
+CONVERSATION_STATE = ConversationState(MEMORY)
+DIALOG = MainDialog(APP.config)
+BOT = DialogAndWelcomeBot(CONVERSATION_STATE, USER_STATE, DIALOG)
-user_state = UserState(memory)
-conversation_state = ConversationState(memory)
-dialog = MainDialog(app.config)
-bot = DialogAndWelcomeBot(conversation_state, user_state, dialog)
-
-@app.route('/api/messages', methods = ['POST'])
+@APP.route('/api/messages', methods=['POST'])
def messages():
-
+ """Main bot message handler."""
if request.headers['Content-Type'] == 'application/json':
body = request.json
else:
@@ -62,19 +44,19 @@ def messages():
activity = Activity().deserialize(body)
auth_header = request.headers['Authorization'] if 'Authorization' in request.headers else ''
-
+
async def aux_func(turn_context):
- asyncio.ensure_future(bot.on_turn(turn_context), loop=loop)
+ asyncio.ensure_future(BOT.on_turn(turn_context))
try:
- task = asyncio.ensure_future(ADAPTER.process_activity(activity, auth_header, aux_func), loop=loop)
- loop.run_until_complete(task)
+ task = LOOP.create_task(ADAPTER.process_activity(activity, auth_header, aux_func))
+ LOOP.run_until_complete(task)
return Response(status=201)
- except Exception as e:
- raise e
+ except Exception as exception:
+ raise exception
-if __name__ == "__main__" :
- try:
- app.run(debug=True, port=app.config["PORT"])
- except Exception as e:
- raise e
+if __name__ == "__main__":
+ try:
+ APP.run(debug=True, port=APP.config["PORT"]) # nosec debug
+ except Exception as exception:
+ raise exception
diff --git a/samples/python-flask/13.core-bot/booking_details.py b/samples/python-flask/13.core-bot/booking_details.py
index 098838966..dbee56240 100644
--- a/samples/python-flask/13.core-bot/booking_details.py
+++ b/samples/python-flask/13.core-bot/booking_details.py
@@ -1,8 +1,11 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
+"""Booking detail."""
class BookingDetails:
+ """Booking detail implementation"""
def __init__(self, destination: str = None, origin: str = None, travel_date: str = None):
self.destination = destination
self.origin = origin
- self.travel_date = travel_date
\ No newline at end of file
+ self.travel_date = travel_date
+
\ No newline at end of file
diff --git a/samples/python-flask/13.core-bot/bots/__init__.py b/samples/python-flask/13.core-bot/bots/__init__.py
index 431b7d8ff..74b723fd8 100644
--- a/samples/python-flask/13.core-bot/bots/__init__.py
+++ b/samples/python-flask/13.core-bot/bots/__init__.py
@@ -1,9 +1,10 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
+"""bots module."""
from .dialog_bot import DialogBot
from .dialog_and_welcome_bot import DialogAndWelcomeBot
__all__ = [
- 'DialogBot',
- 'DialogAndWelcomeBot']
\ No newline at end of file
+ 'DialogBot',
+ 'DialogAndWelcomeBot']
diff --git a/samples/python-flask/13.core-bot/bots/dialog_and_welcome_bot.py b/samples/python-flask/13.core-bot/bots/dialog_and_welcome_bot.py
index cee50366e..357b00fef 100644
--- a/samples/python-flask/13.core-bot/bots/dialog_and_welcome_bot.py
+++ b/samples/python-flask/13.core-bot/bots/dialog_and_welcome_bot.py
@@ -1,45 +1,42 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
-
+"""Main dialog to welcome users."""
import json
import os.path
-
from typing import List
-from botbuilder.core import CardFactory
-from botbuilder.core import ActivityHandler, ConversationState, UserState, TurnContext
-from botbuilder.dialogs import Dialog
+from botbuilder.core import TurnContext
from botbuilder.schema import Activity, Attachment, ChannelAccount
from helpers.activity_helper import create_activity_reply
-
from .dialog_bot import DialogBot
class DialogAndWelcomeBot(DialogBot):
+ """Main dialog to welcome users implementation."""
- def __init__(self, conversation_state: ConversationState, user_state: UserState, dialog: Dialog):
- super(DialogAndWelcomeBot, self).__init__(conversation_state, user_state, dialog)
-
- async def on_members_added_activity(self, members_added: List[ChannelAccount], turn_context: TurnContext):
+ async def on_members_added_activity(self, members_added: List[ChannelAccount],
+ turn_context: TurnContext):
for member in members_added:
# Greet anyone that was not the target (recipient) of this message.
- # To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details.
+ # To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards
+ # for more details.
if member.id != turn_context.activity.recipient.id:
welcome_card = self.create_adaptive_card_attachment()
response = self.create_response(turn_context.activity, welcome_card)
await turn_context.send_activity(response)
-
- # Create an attachment message response.
+
def create_response(self, activity: Activity, attachment: Attachment):
+ """Create an attachment message response."""
response = create_activity_reply(activity)
response.attachments = [attachment]
return response
# Load attachment from file.
def create_adaptive_card_attachment(self):
+ """Create an adaptive card."""
relative_path = os.path.abspath(os.path.dirname(__file__))
path = os.path.join(relative_path, "resources/welcomeCard.json")
- with open(path) as f:
- card = json.load(f)
+ with open(path) as card_file:
+ card = json.load(card_file)
return Attachment(
- content_type= "application/vnd.microsoft.card.adaptive",
- content= card)
\ No newline at end of file
+ content_type="application/vnd.microsoft.card.adaptive",
+ content=card)
diff --git a/samples/python-flask/13.core-bot/bots/dialog_bot.py b/samples/python-flask/13.core-bot/bots/dialog_bot.py
index e9d1dd008..f73b71c72 100644
--- a/samples/python-flask/13.core-bot/bots/dialog_bot.py
+++ b/samples/python-flask/13.core-bot/bots/dialog_bot.py
@@ -1,26 +1,26 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
-
-import asyncio
+"""Implements bot Activity handler."""
from botbuilder.core import ActivityHandler, ConversationState, UserState, TurnContext
from botbuilder.dialogs import Dialog
from helpers.dialog_helper import DialogHelper
class DialogBot(ActivityHandler):
-
- def __init__(self, conversation_state: ConversationState, user_state: UserState, dialog: Dialog):
+ """Main activity handler for the bot."""
+ def __init__(self, conversation_state: ConversationState,
+ user_state: UserState, dialog: Dialog):
if conversation_state is None:
raise Exception('[DialogBot]: Missing parameter. conversation_state is required')
if user_state is None:
raise Exception('[DialogBot]: Missing parameter. user_state is required')
if dialog is None:
raise Exception('[DialogBot]: Missing parameter. dialog is required')
-
+
self.conversation_state = conversation_state
self.user_state = user_state
self.dialog = dialog
- self.dialogState = self.conversation_state.create_property('DialogState')
+ self.dialogState = self.conversation_state.create_property('DialogState') # pylint: disable=C0103
async def on_turn(self, turn_context: TurnContext):
await super().on_turn(turn_context)
@@ -28,6 +28,7 @@ async def on_turn(self, turn_context: TurnContext):
# Save any state changes that might have occured during the turn.
await self.conversation_state.save_changes(turn_context, False)
await self.user_state.save_changes(turn_context, False)
-
+
async def on_message_activity(self, turn_context: TurnContext):
- await DialogHelper.run_dialog(self.dialog, turn_context, self.conversation_state.create_property("DialogState"))
\ No newline at end of file
+ await DialogHelper.run_dialog(self.dialog, turn_context,
+ self.conversation_state.create_property("DialogState")) # pylint: disable=C0103
diff --git a/samples/python-flask/13.core-bot/config.py b/samples/python-flask/13.core-bot/config.py
index 1ac541316..612a2d73d 100644
--- a/samples/python-flask/13.core-bot/config.py
+++ b/samples/python-flask/13.core-bot/config.py
@@ -2,11 +2,13 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
+""" Bot Configuration """
class DefaultConfig(object):
+ """ Bot Configuration """
PORT = 3978
APP_ID = ""
APP_PASSWORD = ""
LUIS_APP_ID = ""
LUIS_API_KEY = ""
+ # LUIS endpoint host name, ie "https://westus.api.cognitive.microsoft.com"
LUIS_API_HOST_NAME = ""
-
diff --git a/samples/python-flask/13.core-bot/dialogs/__init__.py b/samples/python-flask/13.core-bot/dialogs/__init__.py
index 8edc5dc49..1d3c05892 100644
--- a/samples/python-flask/13.core-bot/dialogs/__init__.py
+++ b/samples/python-flask/13.core-bot/dialogs/__init__.py
@@ -1,13 +1,13 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
-
+"""Dialogs module"""
from .booking_dialog import BookingDialog
from .cancel_and_help_dialog import CancelAndHelpDialog
from .date_resolver_dialog import DateResolverDialog
from .main_dialog import MainDialog
__all__ = [
- 'BookingDialog',
- 'CancelAndHelpDialog',
- 'DateResolverDialog',
- 'MainDialog']
\ No newline at end of file
+ 'BookingDialog',
+ 'CancelAndHelpDialog',
+ 'DateResolverDialog',
+ 'MainDialog']
diff --git a/samples/python-flask/13.core-bot/dialogs/booking_dialog.py b/samples/python-flask/13.core-bot/dialogs/booking_dialog.py
index b7247ca9a..fd6464370 100644
--- a/samples/python-flask/13.core-bot/dialogs/booking_dialog.py
+++ b/samples/python-flask/13.core-bot/dialogs/booking_dialog.py
@@ -1,14 +1,18 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
+"""Flight booking dialog."""
from botbuilder.dialogs import WaterfallDialog, WaterfallStepContext, DialogTurnResult
from botbuilder.dialogs.prompts import ConfirmPrompt, TextPrompt, PromptOptions
from botbuilder.core import MessageFactory
+from datatypes_date_time.timex import Timex
from .cancel_and_help_dialog import CancelAndHelpDialog
from .date_resolver_dialog import DateResolverDialog
-from datatypes_date_time.timex import Timex
+
+
class BookingDialog(CancelAndHelpDialog):
+ """Flight booking implementation."""
def __init__(self, dialog_id: str = None):
super(BookingDialog, self).__init__(dialog_id or BookingDialog.__name__)
@@ -25,75 +29,67 @@ def __init__(self, dialog_id: str = None):
]))
self.initial_dialog_id = WaterfallDialog.__name__
-
- """
- If a destination city has not been provided, prompt for one.
- """
- async def destination_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+
+ async def destination_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Prompt for destination."""
booking_details = step_context.options
- if (booking_details.destination is None):
- return await step_context.prompt(TextPrompt.__name__, PromptOptions(prompt= MessageFactory.text('To what city would you like to travel?')))
- else:
+ if booking_details.destination is None:
+ return await step_context.prompt(TextPrompt.__name__,
+ PromptOptions(prompt=MessageFactory.text(
+ 'To what city would you like to travel?'))) # pylint: disable=line-too-long,bad-continuation
+ else:
return await step_context.next(booking_details.destination)
- """
- If an origin city has not been provided, prompt for one.
- """
- async def origin_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ async def origin_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Prompt for origin city."""
booking_details = step_context.options
# Capture the response to the previous step's prompt
booking_details.destination = step_context.result
- if (booking_details.origin is None):
- return await step_context.prompt(TextPrompt.__name__, PromptOptions(prompt= MessageFactory.text('From what city will you be travelling?')))
- else:
+ if booking_details.origin is None:
+ return await step_context.prompt(TextPrompt.__name__,
+ PromptOptions(prompt=MessageFactory.text('From what city will you be travelling?'))) # pylint: disable=line-too-long,bad-continuation
+ else:
return await step_context.next(booking_details.origin)
- """
- If a travel date has not been provided, prompt for one.
- This will use the DATE_RESOLVER_DIALOG.
- """
- async def travel_date_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ async def travel_date_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Prompt for travel date.
+ This will use the DATE_RESOLVER_DIALOG."""
+
booking_details = step_context.options
# Capture the results of the previous step
booking_details.origin = step_context.result
- if (not booking_details.travel_date or self.is_ambiguous(booking_details.travel_date)):
- return await step_context.begin_dialog(DateResolverDialog.__name__, booking_details.travel_date)
- else:
+ if (not booking_details.travel_date or self.is_ambiguous(booking_details.travel_date)):
+ return await step_context.begin_dialog(DateResolverDialog.__name__, booking_details.travel_date) # pylint: disable=line-too-long
+ else:
return await step_context.next(booking_details.travel_date)
- """
- Confirm the information the user has provided.
- """
- async def confirm_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ async def confirm_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Confirm the information the user has provided."""
booking_details = step_context.options
# Capture the results of the previous step
- booking_details.travel_date= step_context.result
- msg = f'Please confirm, I have you traveling to: { booking_details.destination } from: { booking_details.origin } on: { booking_details.travel_date}.'
+ booking_details.travel_date = step_context.result
+ msg = f'Please confirm, I have you traveling to: { booking_details.destination }'\
+ f' from: { booking_details.origin } on: { booking_details.travel_date}.'
# Offer a YES/NO prompt.
- return await step_context.prompt(ConfirmPrompt.__name__, PromptOptions(prompt= MessageFactory.text(msg)))
+ return await step_context.prompt(ConfirmPrompt.__name__,
+ PromptOptions(prompt=MessageFactory.text(msg)))
- """
- Complete the interaction and end the dialog.
- """
async def final_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
-
- if step_context.result:
+ """Complete the interaction and end the dialog."""
+ if step_context.result:
booking_details = step_context.options
- booking_details.travel_date= step_context.result
+ booking_details.travel_date = step_context.result
return await step_context.end_dialog(booking_details)
- else:
+ else:
return await step_context.end_dialog()
- def is_ambiguous(self, timex: str) -> bool:
+ def is_ambiguous(self, timex: str) -> bool:
+ """Ensure time is correct."""
timex_property = Timex(timex)
return 'definite' not in timex_property.types
-
-
-
-
\ No newline at end of file
diff --git a/samples/python-flask/13.core-bot/dialogs/cancel_and_help_dialog.py b/samples/python-flask/13.core-bot/dialogs/cancel_and_help_dialog.py
index 70e078cbb..0e9010ffb 100644
--- a/samples/python-flask/13.core-bot/dialogs/cancel_and_help_dialog.py
+++ b/samples/python-flask/13.core-bot/dialogs/cancel_and_help_dialog.py
@@ -1,22 +1,19 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
-
+"""Handle cancel and help intents."""
from botbuilder.dialogs import ComponentDialog, DialogContext, DialogTurnResult, DialogTurnStatus
from botbuilder.schema import ActivityTypes
class CancelAndHelpDialog(ComponentDialog):
-
- def __init__(self, dialog_id: str):
- super(CancelAndHelpDialog, self).__init__(dialog_id)
-
+ """Implementation of handling cancel and help."""
async def on_begin_dialog(self, inner_dc: DialogContext, options: object) -> DialogTurnResult:
result = await self.interrupt(inner_dc)
if result is not None:
return result
return await super(CancelAndHelpDialog, self).on_begin_dialog(inner_dc, options)
-
+
async def on_continue_dialog(self, inner_dc: DialogContext) -> DialogTurnResult:
result = await self.interrupt(inner_dc)
if result is not None:
@@ -25,6 +22,7 @@ async def on_continue_dialog(self, inner_dc: DialogContext) -> DialogTurnResult:
return await super(CancelAndHelpDialog, self).on_continue_dialog(inner_dc)
async def interrupt(self, inner_dc: DialogContext) -> DialogTurnResult:
+ """Detect interruptions."""
if inner_dc.context.activity.type == ActivityTypes.message:
text = inner_dc.context.activity.text.lower()
@@ -36,4 +34,4 @@ async def interrupt(self, inner_dc: DialogContext) -> DialogTurnResult:
await inner_dc.context.send_activity("Cancelling")
return await inner_dc.cancel_all_dialogs()
- return None
\ No newline at end of file
+ return None
diff --git a/samples/python-flask/13.core-bot/dialogs/date_resolver_dialog.py b/samples/python-flask/13.core-bot/dialogs/date_resolver_dialog.py
index fc4a07bb7..ba910c7d2 100644
--- a/samples/python-flask/13.core-bot/dialogs/date_resolver_dialog.py
+++ b/samples/python-flask/13.core-bot/dialogs/date_resolver_dialog.py
@@ -1,55 +1,63 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
-
+"""Handle date/time resolution for booking dialog."""
from botbuilder.core import MessageFactory
from botbuilder.dialogs import WaterfallDialog, DialogTurnResult, WaterfallStepContext
-from botbuilder.dialogs.prompts import DateTimePrompt, PromptValidatorContext, PromptOptions, DateTimeResolution
-from .cancel_and_help_dialog import CancelAndHelpDialog
+from botbuilder.dialogs.prompts import DateTimePrompt, PromptValidatorContext, \
+ PromptOptions, DateTimeResolution
from datatypes_date_time.timex import Timex
-class DateResolverDialog(CancelAndHelpDialog):
+from .cancel_and_help_dialog import CancelAndHelpDialog
+class DateResolverDialog(CancelAndHelpDialog):
+ """Resolve the date"""
def __init__(self, dialog_id: str = None):
super(DateResolverDialog, self).__init__(dialog_id or DateResolverDialog.__name__)
- self.add_dialog(DateTimePrompt(DateTimePrompt.__name__, DateResolverDialog.datetime_prompt_validator))
+ self.add_dialog(DateTimePrompt(DateTimePrompt.__name__,
+ DateResolverDialog.datetime_prompt_validator))
self.add_dialog(WaterfallDialog(WaterfallDialog.__name__ + '2', [
- self.initialStep,
- self.finalStep
+ self.initial_step,
+ self.final_step
]))
self.initial_dialog_id = WaterfallDialog.__name__ + '2'
-
- async def initialStep(self,step_context: WaterfallStepContext) -> DialogTurnResult:
+
+ async def initial_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Prompt for the date."""
timex = step_context.options
prompt_msg = 'On what date would you like to travel?'
- reprompt_msg = "I'm sorry, for best results, please enter your travel date including the month, day and year."
+ reprompt_msg = "I'm sorry, for best results, please enter your travel "\
+ "date including the month, day and year."
if timex is None:
# We were not given any date at all so prompt the user.
- return await step_context.prompt(DateTimePrompt.__name__ ,
- PromptOptions(
- prompt= MessageFactory.text(prompt_msg),
- retry_prompt= MessageFactory.text(reprompt_msg)
+ return await step_context.prompt(DateTimePrompt.__name__,
+ PromptOptions( # pylint: disable=bad-continuation
+ prompt=MessageFactory.text(prompt_msg),
+ retry_prompt=MessageFactory.text(reprompt_msg)
))
else:
# We have a Date we just need to check it is unambiguous.
if 'definite' in Timex(timex).types:
# This is essentially a "reprompt" of the data we were given up front.
- return await step_context.prompt(DateTimePrompt.__name__, PromptOptions(prompt= reprompt_msg))
+ return await step_context.prompt(DateTimePrompt.__name__,
+ PromptOptions(prompt=reprompt_msg))
else:
- return await step_context.next(DateTimeResolution(timex= timex))
+ return await step_context.next(DateTimeResolution(timex=timex))
- async def finalStep(self, step_context: WaterfallStepContext):
+ async def final_step(self, step_context: WaterfallStepContext):
+ """Cleanup - set final return value and end dialog."""
timex = step_context.result[0].timex
return await step_context.end_dialog(timex)
-
+
@staticmethod
async def datetime_prompt_validator(prompt_context: PromptValidatorContext) -> bool:
+ """ Validate the date provided is in proper form. """
if prompt_context.recognized.succeeded:
timex = prompt_context.recognized.value[0].timex.split('T')[0]
- #TODO: Needs TimexProperty
+ # TODO: Needs TimexProperty
return 'definite' in Timex(timex).types
-
+
return False
diff --git a/samples/python-flask/13.core-bot/dialogs/main_dialog.py b/samples/python-flask/13.core-bot/dialogs/main_dialog.py
index db42d586c..f28d9993c 100644
--- a/samples/python-flask/13.core-bot/dialogs/main_dialog.py
+++ b/samples/python-flask/13.core-bot/dialogs/main_dialog.py
@@ -1,17 +1,16 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
-
-from datetime import datetime
-from botbuilder.dialogs import ComponentDialog, DialogSet, DialogTurnStatus, WaterfallDialog, WaterfallStepContext, DialogTurnResult
-from botbuilder.dialogs.prompts import TextPrompt, ConfirmPrompt, PromptOptions
+"""Main dialog. """
+from botbuilder.dialogs import ComponentDialog, WaterfallDialog, \
+ WaterfallStepContext, DialogTurnResult
+from botbuilder.dialogs.prompts import TextPrompt, PromptOptions
from botbuilder.core import MessageFactory
-from .booking_dialog import BookingDialog
from booking_details import BookingDetails
from helpers.luis_helper import LuisHelper
-from datatypes_date_time.timex import Timex
+from .booking_dialog import BookingDialog
class MainDialog(ComponentDialog):
-
+ """Main dialog. """
def __init__(self, configuration: dict, dialog_id: str = None):
super(MainDialog, self).__init__(dialog_id or MainDialog.__name__)
@@ -26,38 +25,44 @@ def __init__(self, configuration: dict, dialog_id: str = None):
]))
self.initial_dialog_id = 'WFDialog'
-
- async def intro_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
- if (not self._configuration.get("LUIS_APP_ID", "") or not self._configuration.get("LUIS_API_KEY", "") or not self._configuration.get("LUIS_API_HOST_NAME", "")):
- await step_context.context.send_activity(
- MessageFactory.text("NOTE: LUIS is not configured. To enable all capabilities, add 'LUIS_APP_ID', 'LUIS_API_KEY' and 'LUIS_API_HOST_NAME' to the config.py file."))
- return await step_context.next(None)
- else:
- return await step_context.prompt(TextPrompt.__name__, PromptOptions(prompt = MessageFactory.text("What can I help you with today?")))
+ async def intro_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
+ """Initial prompt."""
+ return await step_context.prompt(TextPrompt.__name__, PromptOptions(
+ prompt=MessageFactory.text("What can I help you with today?")))
async def act_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
- # Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.)
- booking_details = await LuisHelper.excecute_luis_query(self._configuration, step_context.context) if step_context.result is not None else BookingDetails()
+ """Use language understanding to gather details about booking."""
+
- # In this sample we only have a single Intent we are concerned with. However, typically a scenario
- # will have multiple different Intents each corresponding to starting a different child Dialog.
+ # In this sample we only have a single Intent we are concerned with.
+ # However, typically a scenario will have multiple different Intents
+ # each corresponding to starting a different child Dialog.
+ booking_details = await LuisHelper.execute_luis_query(self._configuration,\
+ step_context.context) if step_context.result is not None else BookingDetails()
- # Run the BookingDialog giving it whatever details we have from the LUIS call, it will fill out the remainder.
+
+ # Run the BookingDialog giving it whatever details we have from the
+ # model. The dialog will prompt to find out the remaining details.
return await step_context.begin_dialog(BookingDialog.__name__, booking_details)
async def final_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
- # If the child dialog ("BookingDialog") was cancelled or the user failed to confirm, the Result here will be null.
- if (step_context.result is not None):
+ """Complete dialog.
+ At this step, with details from the user, display the completed
+ flight booking to the user.
+ """
+ # If the child dialog ("BookingDialog") was cancelled or the user failed
+ # to confirm, the Result here will be null.
+ if step_context.result is not None:
result = step_context.result
# Now we have all the booking details call the booking service.
-
# If the call to the booking service was successful tell the user.
#time_property = Timex(result.travel_date)
#travel_date_msg = time_property.to_natural_language(datetime.now())
- msg = f'I have you booked to {result.destination} from {result.origin} on {result.travel_date}'
+ msg = f'I have you booked to {result.destination} from'\
+ f' {result.origin} on {result.travel_date}.'
await step_context.context.send_activity(MessageFactory.text(msg))
else:
await step_context.context.send_activity(MessageFactory.text("Thank you."))
diff --git a/samples/python-flask/13.core-bot/helpers/__init__.py b/samples/python-flask/13.core-bot/helpers/__init__.py
index a03686074..7ed8a466a 100644
--- a/samples/python-flask/13.core-bot/helpers/__init__.py
+++ b/samples/python-flask/13.core-bot/helpers/__init__.py
@@ -1,9 +1,11 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
+"""Helpers module."""
from . import activity_helper, luis_helper, dialog_helper
__all__ = [
- 'activity_helper',
- 'dialog_helper',
- 'luis_helper']
\ No newline at end of file
+ 'activity_helper',
+ 'dialog_helper',
+ 'luis_helper']
+
\ No newline at end of file
diff --git a/samples/python-flask/13.core-bot/helpers/activity_helper.py b/samples/python-flask/13.core-bot/helpers/activity_helper.py
index 043792f15..12dd60143 100644
--- a/samples/python-flask/13.core-bot/helpers/activity_helper.py
+++ b/samples/python-flask/13.core-bot/helpers/activity_helper.py
@@ -1,22 +1,26 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
+"""Helper to create reply object."""
from datetime import datetime
from botbuilder.schema import Activity, ActivityTypes, ChannelAccount, ConversationAccount
def create_activity_reply(activity: Activity, text: str = None, locale: str = None):
-
+ """Helper to create reply object."""
return Activity(
- type = ActivityTypes.message,
- timestamp = datetime.utcnow(),
- from_property = ChannelAccount(id= getattr(activity.recipient, 'id', None), name= getattr(activity.recipient, 'name', None)),
- recipient = ChannelAccount(id= activity.from_property.id, name= activity.from_property.name),
- reply_to_id = activity.id,
- service_url = activity.service_url,
- channel_id = activity.channel_id,
- conversation = ConversationAccount(is_group= activity.conversation.is_group, id= activity.conversation.id, name= activity.conversation.name),
- text = text or '',
- locale = locale or '',
- attachments = [],
- entities = []
- )
\ No newline at end of file
+ type=ActivityTypes.message,
+ timestamp=datetime.utcnow(),
+ from_property=ChannelAccount(id=getattr(activity.recipient, 'id', None),
+ name=getattr(activity.recipient, 'name', None)),
+ recipient=ChannelAccount(id=activity.from_property.id, name=activity.from_property.name),
+ reply_to_id=activity.id,
+ service_url=activity.service_url,
+ channel_id=activity.channel_id,
+ conversation=ConversationAccount(is_group=activity.conversation.is_group,
+ id=activity.conversation.id,
+ name=activity.conversation.name),
+ text=text or '',
+ locale=locale or '',
+ attachments=[],
+ entities=[]
+ )
diff --git a/samples/python-flask/13.core-bot/helpers/dialog_helper.py b/samples/python-flask/13.core-bot/helpers/dialog_helper.py
index ad78abc98..550a17b5f 100644
--- a/samples/python-flask/13.core-bot/helpers/dialog_helper.py
+++ b/samples/python-flask/13.core-bot/helpers/dialog_helper.py
@@ -1,17 +1,19 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
-
+"""Utility to run dialogs."""
from botbuilder.core import StatePropertyAccessor, TurnContext
from botbuilder.dialogs import Dialog, DialogSet, DialogTurnStatus
class DialogHelper:
+ """Dialog Helper implementation."""
@staticmethod
- async def run_dialog(dialog: Dialog, turn_context: TurnContext, accessor: StatePropertyAccessor):
+ async def run_dialog(dialog: Dialog, turn_context: TurnContext, accessor: StatePropertyAccessor): # pylint: disable=line-too-long
+ """Run dialog."""
dialog_set = DialogSet(accessor)
dialog_set.add(dialog)
dialog_context = await dialog_set.create_context(turn_context)
results = await dialog_context.continue_dialog()
if results.status == DialogTurnStatus.Empty:
- await dialog_context.begin_dialog(dialog.id)
\ No newline at end of file
+ await dialog_context.begin_dialog(dialog.id)
diff --git a/samples/python-flask/13.core-bot/helpers/luis_helper.py b/samples/python-flask/13.core-bot/helpers/luis_helper.py
index 0a3195529..899f813aa 100644
--- a/samples/python-flask/13.core-bot/helpers/luis_helper.py
+++ b/samples/python-flask/13.core-bot/helpers/luis_helper.py
@@ -1,16 +1,21 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
+
+"""Helper to call LUIS service."""
from botbuilder.ai.luis import LuisRecognizer, LuisApplication
from botbuilder.core import TurnContext
from booking_details import BookingDetails
+# pylint: disable=line-too-long
class LuisHelper:
-
+ """LUIS helper implementation."""
@staticmethod
- async def excecute_luis_query(configuration: dict, turn_context: TurnContext) -> BookingDetails:
+ async def execute_luis_query(configuration: dict, turn_context: TurnContext) -> BookingDetails:
+ """Invoke LUIS service to perform prediction/evaluation of utterance."""
booking_details = BookingDetails()
+ # pylint:disable=broad-except
try:
luis_application = LuisApplication(
configuration['LUIS_APP_ID'],
@@ -26,20 +31,17 @@ async def excecute_luis_query(configuration: dict, turn_context: TurnContext) ->
if intent == 'Book_flight':
# We need to get the result from the LUIS JSON which at every level returns an array.
to_entities = recognizer_result.entities.get("$instance", {}).get("To", [])
- if len(to_entities) > 0:
+ if to_entities:
booking_details.destination = to_entities[0]['text']
from_entities = recognizer_result.entities.get("$instance", {}).get("From", [])
- if len(from_entities) > 0:
+ if from_entities:
booking_details.origin = from_entities[0]['text']
- # TODO: This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the Time part.
+ # This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the Time part.
# TIMEX is a format that represents DateTime expressions that include some ambiguity. e.g. missing a Year.
date_entities = recognizer_result.entities.get("$instance", {}).get("datetime", [])
- if len(date_entities) > 0:
- text = date_entities[0]['text']
- booking_details.travel_date = None # TODO: Set when we get a timex format
- except Exception as e:
- print(e)
-
+ if date_entities:
+ booking_details.travel_date = None # Set when we get a timex format
+ except Exception as exception:
+ print(exception)
return booking_details
-