How can the Amazon Echo catch errors? - python

I am building an application for the Amazon Echo in python. When I speak a bad utterance that the Amazon Echo does not recognize, my skill quits and returns me to the home screen. I am looking to prevent this and repeat what was just uttered by the Amazon Echo.
To try to achieve this to some extent I try calling a function to say something when the session ends or bad input is detected.
def on_session_ended(session_ended_request, session):
"""
Called when the user ends the session.
Is not called when the skill returns should_end_session=true
"""
print("on_session_ended requestId=" + session_ended_request['requestId'] +
", sessionId=" + session['sessionId'])
return get_session_end_response()
However, I just get an error from the Echo -- this function, on_session_ended is never entered.
So how do I conduct error catching and handling on the Amazon Echo?
UPDATE 1: I reduced the number of utterances and the number of intents with custom slots to one. Now a user should only speak A, B, C, or D. If they speak anything outside of this, then the intent is still triggered but with no slot value. Thus, I can do some error checking based on whether the slot value is there or not. However, this seems like not the best way to do it. When I try to add in intents with no slots and a corresponding utterance, anything that doesn't match either of my intents defaults to this new intent. How can I resolve these issues?
UPDATE 2: Here are some relevant sections of my code.
Intent handlers:
def lambda_handler(event, context):
print("Python START -------------------------------")
print("event.session.application.applicationId=" +
event['session']['application']['applicationId'])
if event['session']['new']:
on_session_started({'requestId': event['request']['requestId']},
event['session'])
if event['request']['type'] == "LaunchRequest":
return on_launch(event['request'], event['session'])
elif event['request']['type'] == "IntentRequest":
return on_intent(event['request'], event['session'])
elif event['request']['type'] == "SessionEndedRequest":
return on_session_ended(event['request'], event['session'])
def on_session_started(session_started_request, session):
print("on_session_started requestId=" + session_started_request['requestId']
+ ", sessionId=" + session['sessionId'])
def on_launch(launch_request, session):
""" Called when the user launches the skill without specifying what they want """
print("on_launch requestId=" + launch_request['requestId'] +
", sessionId=" + session['sessionId'])
# Dispatch to your skill's launch
return create_new_user()
def on_intent(intent_request, session):
""" Called when the user specifies an intent for this skill """
print("on_intent requestId=" + intent_request['requestId'] +
", sessionId=" + session['sessionId'])
intent = intent_request['intent']
intent_name = intent['name']
attributes = session["attributes"] if 'attributes' in session else None
intent_slots = intent['slots'] if 'slots' in intent else None
# Dispatch to skill's intent handlers
# TODO : Authenticate users
# TODO : Start session in a different spot depending on where user left off
if intent_name == "StartQuizIntent":
return create_new_user()
elif intent_name == "AnswerIntent":
return get_answer_response(intent_slots, attributes)
elif intent_name == "TestAudioIntent":
return get_audio_response()
elif intent_name == "AMAZON.HelpIntent":
return get_help_response()
elif intent_name == "AMAZON.CancelIntent":
return get_session_end_response()
elif intent_name == "AMAZON.StopIntent":
return get_session_end_response()
else:
return get_session_end_response()
def on_session_ended(session_ended_request, session):
"""
Called when the user ends the session.
Is not called when the skill returns should_end_session=true
"""
print("on_session_ended requestId=" + session_ended_request['requestId'] +
", sessionId=" + session['sessionId'])
return get_session_end_response()
Then we have the functions that actually get called and the response builders. I have edited some of the code for privacy. I haven't built up all the display response text fields and have some uids hard coded so I don't have to worry about authentication yet.
# --------------- Functions that control the skill's behavior ------------------
####### GLOBAL SETTINGS ########
utility_background_image = "https://i.imgur.com/XXXX.png"
def get_welcome_response():
""" Returns the welcome message if a user invokes the skill without specifying an intent """
session_attributes = {}
card_title = ""
speech_output = ("Hello and welcome ... quiz .... blah blah ...")
reprompt_text = "Ask me to start and we will begin the test!"
should_end_session = False
# visual responses
primary_text = '' # TODO
secondary_text = '' # TODO
return build_response(session_attributes,
build_speechlet_response(card_title, speech_output, reprompt_text,
should_end_session,
build_display_response(utility_background_image,
card_title, primary_text,
secondary_text)))
def get_session_end_response():
""" Returns the ending message if a user errs or exits the skill """
session_attributes = {}
card_title = ""
speech_output = "Thank you for your time!"
reprompt_text = ''
should_end_session = True
# visual responses
primary_text = '' # TODO
secondary_text = '' # TODO
return build_response(session_attributes,
build_speechlet_response(card_title, speech_output, reprompt_text,
should_end_session,
build_display_response(utility_background_image,
card_title, primary_text,
secondary_text)))
def get_audio_response():
""" Tests the audio capabilities of the echo """
session_attributes = {}
card_title = "" # TODO : keep no 'welcome'?
speech_output = ""
reprompt_text = ""
should_end_session = False
# visual responses
primary_text = '' # TODO
secondary_text = '' # TODO
return build_response(session_attributes,
build_speechlet_response(card_title, speech_output, reprompt_text,
should_end_session, build_audio_response()))
def create_new_user():
""" Creates a new user that the server will recognize and whose action will be stored in db """
url = "http://XXXXXX:XXXX/create_user"
response = urllib.request.urlopen(url)
data = json.loads(response.read().decode('utf8'))
uuid = data["uuid"]
return ask_question(uuid)
def query_server(uuid):
""" Requests to get num_questions number of questions from the server """
url = "http://XXXXXXXX:XXXX/get_question_json?uuid=%s" % (uuid) # TODO : change needs to to be uuid
response = urllib.request.urlopen(url)
data = json.loads(response.read().decode('utf8'))
if data["status"]:
question = data["data"]["question"]
quid = data["data"]["quid"]
next_quid = data["data"]["next_quid"] # TODO : will we need any of this?
topic = data["data"]["topic"]
type = data["data"]["type"]
media_type = data["data"]["media_type"] # either 'IMAGE', 'AUDIO', or 'VIDEO'
answers = data["data"]["answer"] # list of answers stored in order they should be spoken
images = data["data"]["image"] # list of images that correspond to order of answers list
audio = data["data"]["audio"]
video = data["data"]["video"]
question_data = {"status": True, "data":{"question": question, "quid": quid, "answers": answers,
"media_type": media_type, "images": images, "audio": audio, "video": video}}
if next_quid is "None":
return None
return question_data
else:
return {"status": False}
def ask_question(uuid):
""" Returns a quiz question to the user since they specified a QuizIntent """
question_data = query_server(uuid)
if question_data is None:
return get_session_end_response()
card_title = "Ask Question"
speech_output = ""
session_attributes = {}
should_end_session = False
reprompt_text = ""
# visual responses
display_title = ""
primary_text = ""
secondary_text = ""
images = []
answers = []
if question_data["status"]:
session_attributes = {
"quid": question_data["data"]["quid"],
"uuid": "df876c9d-cd41-4b9f-a3b9-3ccd1b441f24",
"question_start_time": time.time()
}
question = question_data["data"]["question"]
answers = question_data["data"]["answers"] # answers are shuffled when pulled from server
images = question_data["data"]["images"]
# TODO : consider different media types
speech_output += question
reprompt_text += ("Please choose an answer using the official NATO alphabet. For example," +
" A is alpha, B is bravo, and C is charlie.")
else:
speech_output += "Oops! This is embarrassing. There seems to be a problem with the server."
reprompt_text += "I don't exactly know where to go from here. I suggest restarting this skill."
return build_response(session_attributes, build_speechlet_response(card_title, speech_output,
reprompt_text, should_end_session,
build_display_response_list_template2(title=question, image_urls=images, answers=answers)))
def send_quiz_responses_to_server(uuid, quid, time_used_for_question, answer_given):
""" Sends the users responses back to the server to be stored in the database """
url = ("http://XXXXXXXX:XXXX/send_answers?uuid=%s&quid=%s&time=%s&answer_given=%s" %
(uuid, quid, time_used_for_question, answer_given))
response = urllib.request.urlopen(url)
data = json.loads(response.read().decode('utf8'))
return data["status"]
def get_answer_response(slots, attributes):
""" Returns a correct/incorrect message to the user depending on their AnswerIntent """
# get time, quid, and uuid from attributes
question_start_time = attributes["question_start_time"]
quid = attributes["quid"]
uuid = attributes["uuid"]
# get answer from slots
try:
answer_given = slots["Answer"]["value"].lower()
except KeyError:
return get_session_end_response()
# calculate a rough estimate of the time it took to answer question
time_used_for_question = str(int(time.time() - question_start_time))
# record response data by sending it to the server
send_quiz_responses_to_server(uuid, quid, time_used_for_question, answer_given)
return ask_question(uuid)
def get_help_response():
""" Returns a help message to the user since they called AMAZON.HelpIntent """
session_attributes = {}
card_title = ""
speech_output = "" # TODO
reprompt_text = "" # TODO
should_end_session = False
return build_response(session_attributes,
build_speechlet_response(card_title, speech_output, reprompt_text, should_end_session,
build_display_response(utility_background_image, card_title)))
# --------------- Helpers that build all of the responses ----------------------
def build_hint_response(hint):
"""
Builds the hint response for a display.
For example, Try "Alexa, play number 1" where "play number 1" is the hint.
"""
return {
"type": "Hint",
"hint": {
"type": "RichText",
"text": hint
}
}
def build_display_response(url='', title='', primary_text='', secondary_text='', tertiary_text=''):
"""
Builds the display template for the echo show to display.
Echo show screen is 1024px x 600px
For additional image size requirements, see the display interface reference.
"""
return [{
"type": "Display.RenderTemplate",
"template": {
"type": "BodyTemplate1",
"token": "question",
"title": title,
"backgroundImage": {
"contentDescription": "Question",
"sources": [
{
"url": url
}
]
},
"textContent": {
"primaryText": {
"type": "RichText",
"text": primary_text
},
"secondaryText": {
"type": "RichText",
"text": secondary_text
},
"tertiaryText": {
"type": "RichText",
"text": tertiary_text
}
}
}
}]
def build_list_item(url='', primary_text='', secondary_text='', tertiary_text=''):
return {
"token": "question_item",
"image": {
"sources": [
{
"url": url
}
],
"contentDescription": "Question Image"
},
"textContent": {
"primaryText": {
"type": "RichText",
"text": primary_text
},
"secondaryText": {
"text": secondary_text,
"type": "PlainText"
},
"tertiaryText": {
"text": tertiary_text,
"type": "PlainText"
}
}
}
def build_display_response_list_template2(title='', image_urls=[], answers=[]):
list_items = []
for image, answer in zip(image_urls, answers):
list_items.append(build_list_item(url=image, primary_text=answer))
return [{
"type": "Display.RenderTemplate",
"template": {
"type": "ListTemplate2",
"token": "question",
"title": title,
"backgroundImage": {
"contentDescription": "Question Background",
"sources": [
{
"url": "https://i.imgur.com/HkaPLrK.png"
}
]
},
"listItems": list_items
}
}]
def build_audio_response(url): # TODO add a display repsonse here as well
""" Builds audio response. I.e. plays back an audio file with zero offset """
return [{
"type": "AudioPlayer.Play",
"playBehavior": "REPLACE_ALL",
"audioItem": {
"stream": {
"token": "audio_clip",
"url": url,
"offsetInMilliseconds": 0
}
}
}]
def build_speechlet_response(title, output, reprompt_text, should_end_session, directive=None):
""" Builds speechlet response and puts display response inside """
return {
'outputSpeech': {
'type': 'PlainText',
'text': output
},
'card': {
'type': 'Simple',
'title': title,
'content': output
},
'reprompt': {
'outputSpeech': {
'type': 'PlainText',
'text': reprompt_text
}
},
'shouldEndSession': should_end_session,
'directives': directive
}
def build_response(session_attributes, speechlet_response):
""" Builds the complete response to send back to Alexa """
return {
'version': '1.0',
'sessionAttributes': session_attributes,
'response': speechlet_response
}
UPDATE 3: I updated the intents so there is now one custom intent that takes a custom slot, and then I have another custom intent that takes no slots. These custom intents also have there own sample utterances. Both the intents and their utterances are listed below. When I start the skill, it works fine. Then when I say/type "zoo zoo zoo" to test bad input, I get an error. Both the request for "zoo zoo zoo" and the response are listed below. I am looking for a good way to catch this bad input error and resume/revert the skill back to its previous state.
Intents:
...
{
"intent": "TestAudioIntent"
},
{
"slots": [
{
"name": "Answer",
"type": "LETTER"
}
],
"intent": "AnswerIntent"
},
...
Sample Utterances:
AnswerIntent {Answer}
AnswerIntent I think it is {Answer}
TestAudioIntent test the audio
Example JSON request:
{
"session": {
"new": false,
"sessionId": "SessionId.574f0b74-be17-4f79-bbd6-ce926a1bf856",
"application": {
"applicationId": "XXXXXXXX"
},
"attributes": {
"quid": "7fa9fcbf-35db-4bbd-ac73-37977bcef563",
"question_start_time": 1515691612.7381804,
"uuid": "df876c9d-cd41-4b9f-a3b9-3ccd1b441f24"
},
"user": {
"userId": "XXXXXXXX"
}
},
"request": {
"type": "IntentRequest",
"requestId": "EdwRequestId.23765cb0-f327-4f52-a9a3-b9f92a375a5f",
"intent": {
"name": "TestAudioIntent",
"slots": {}
},
"locale": "en-US",
"timestamp": "2018-01-11T17:26:57Z"
},
"context": {
"AudioPlayer": {
"playerActivity": "IDLE"
},
"System": {
"application": {
"applicationId": "XXXXXXXX"
},
"user": {
"userId": "XXXXXXXX"
},
"device": {
"supportedInterfaces": {
"Display": {
"templateVersion": "1",
"markupVersion": "1"
}
}
}
}
},
"version": "1.0"
}
And I get the following testing error as a response:
The remote endpoint could not be called, or the response it returned was invalid.

What I ended up doing is using something similar to Amazon's dialogue management system. If a user says something that doesn't fill a slot, I re-prompt them with that question. My goal is to record a user's statements/answers after each time they speak, thus I didn't use the built-in dialogue management. Additionally, I used Amazon's slot synonyms for all my slots to make my modal more robust.
I still don't know that this is the best way, but it is a starting point and seems to work O.K....

Related

TypeError when trying to set a DB value

I'm trying to get my Flask server to update to a Mongo Atlas DB on request. When a request is passed with the required arguments, it will post to a Discord webhook then attempt to update DB values.
Example: Going to https://(redacted for privacy)/(redacted for privacy)?iid=123&username=wow&price=22 with JUST the webhook code (doesn't touch DB) will do this:
(sent to discord webhook) and give a success message (outputs as 200 in the console).
But when I enable the DB code, the same link will through a 500 error. Anything I can do? The console outputs as;
balance = mycol.find_one({"UserID": uid})['balance']
TypeError: 'NoneType' object is not subscriptable
I don't understand why this won't work. Here's the code that works, but only posts to the webhook (commented stuff is DB related):
#balance = mycol.find_one({"UserID": uid})['balance']
#res = mycol.find_one({"UserID": uid})
#newvalues = { "$set": { "UserID": uid, "balance": int(balance) + int(cashback_amt)} }
#mycol.update_one(res, newvalues)
img_url = f"https://www.roblox.com/bust-thumbnail/image?userId={uid}&width=420&height=420&format=png"
data = {
"content" : "**New purchase!**",
"username" : "robate"
}
data["embeds"] = [
{
"description" : f"**ItemID**: `{item_id}`\n**Item Price**: `{price}`\n**Item Cashback**: `{cashback_amt}`",
"title" : "New purchase made",
"color": 65311,
"thumbnail": {"url": img_url}
}
]
result = requests.post(purclog, json = data)
return jsonify({"res": True})
And the non-working DB included code:
balance = mycol.find_one({"UserID": uid})['balance']
res = mycol.find_one({"UserID": uid})
newvalues = { "$set": { "UserID": uid, "balance": int(balance) + int(cashback_amt)} }
mycol.update_one(res, newvalues)
img_url = f"https://www.roblox.com/bust-thumbnail/image?userId={uid}&width=420&height=420&format=png"
data = {
"content" : "**New purchase!**",
"username" : "robate"
}
data["embeds"] = [
{
"description" : f"**ItemID**: `{item_id}`\n**Item Price**: `{price}`\n**Item Cashback**: `{cashback_amt}`",
"title" : "New purchase made",
"color": 65311,
"thumbnail": {"url": img_url}
}
]
result = requests.post(purclog, json = data)
return jsonify({"res": True})
Any help is severely appreciated. Have a good day!
EDIT: The UserID is defined here:
try:
uid = requests.get(f"https://api.roblox.com/users/get-by-username?username={username}").json()['Id']
except:
return
balance = mycol.find_one({"UserID": uid})['balance']

Why Dictionary Flag is working biased in python?

Why flagSelectTask is working sometimes and sometimes not?
In the code below, When I type /reminder in chatbot the flag should be set to True and when I open webview it should check the condition and result accordingly. But If I clicks multiple times after closing a webview It returns either if block or else block. Why this is happening?
class Bot:
flagSelectTask = {}
def handleTextMessage(self,recipientID,messagingEvent):
if textMessage.lower() == '/reminder':
Bot.flagSelectTask[recipientID] = True # Select Task Webview Access Permission Granted
return self.selectTaskWebView(recipientID)
def selectTaskWebView(self,assignerID):
messageData = {
"recipient":{
"id":assignerID
},
"message": {
"attachment": {
"type": "template",
"payload": {
"template_type": "generic",
"elements": [
{
"title": "Please Select a Task",
"buttons": [
{
"type": "web_url",
"title": "Select",
"url": f"url.com/select-task/{assignerID}",
"webview_height_ratio": "tall",
"messenger_extensions": "true",
},
],
},
],
},
}}}
return self.sendAPI(messageData)
bot = Bot()
#app.route('/select-task/<string:assignerID>',methods=['GET'])
def selectTaskView(assignerID):
if (assignerID in bot.flagSelectTask) and (bot.flagSelectTask[assignerID]):
Task = bot.loadTasks(assignerID)
log(Task)
if len(Task)!=0:
return render_template('tasks.html',tasks=Task)
else:
return '<b>You have not assigned any task yet</b>'
else:
return '<h1> Thank You. Please Restart If you want to send a reminder again.</h1>'

Is there a way to add entries to a dictionary in a list in json?

Is there a way to add entries to a dictionary in a list in json? (P.S. I'm not even sure if that's how I'm supposed to phrase the question)
base_server_datas = {
'users': {},
}
#commands.command()
#commands.has_any_role(*MODERATORS, *ADMINISTRATORS)
async def create(self, ctx, *args: str):
faction = load_faction()
message = " ".join(args).lower()
new_faction = message
if new_faction not in faction:
faction[new_faction] = base_server_datas.copy()
save_faction(faction)
await ctx.send(f'**{new_faction}** cult created!')
else:
await ctx.send(f"{new_faction} cult already exists")
#commands.command(case_insensitive=True)
async def join(self, ctx, *args: str):
faction = load_faction()
new_user = str(ctx.author.id)
message = " ".join(args).lower()
existing_faction = message
if existing_faction in faction:
if new_user not in faction[existing_faction]["users"]:
faction[existing_faction]["users"][new_user] = ctx.author.name
save_faction(faction)
await ctx.send(f"{ctx.author.mention} joined the **{message}** cult")
else:
await ctx.send(f"{ctx.author.mention} is already in the cult")
else:
await ctx.send("faction doesn\'t exist")
so using this code I can create a faction and can join it as well, it looks something like this:
{
"h": {
"users": {
"535485026905882626": "Benji",
"702646374155419648": "rosesaredumb",
"578938036960624660": "invalid-user",
"360366991149891585": "Goodra999"
}
}
}
but I made another title "leaders" and tried adding entries under it, but it doesn't work
It looks something like this
{
"h": [
{
"users": {}
},
{
"leaders": {}
}
]
}
the code for this part is
base_server_datas2 = {
'leaders': {},
}
#commands.command()
async def leader(self,ctx, member: discord.Member, *args: str):
faction = load_faction()
new_user = str(member.id)
message = " ".join(args).lower()
existing_faction = message
if existing_faction in faction:
if new_user not in faction[existing_faction]["leaders"]:
faction[existing_faction]["leaders"][new_user] = member.name
save_faction(faction)
await ctx.send(f"{ctx.author.mention} joined the **{message}** cult")
else:
await ctx.send(f"{ctx.author.mention} is already in the cult")
else:
await ctx.send("faction doesn\'t exist")
On adding the "leaders" title, now I can't even update the "users" dictionary
Is there any way I can add entries to both the dictionaries?
edit: this is the output I want
{
"h": [
{
"users": {
"702646374155419648": "rosesaredumb",
"578938036960624660": "invalid-user"
}
},
{
"leaders": {
"57893803696076560": "josh"
}
}
]
}
first convert the dictionary d["h"] to a list including the current value and add the required leader's dictionary to the list:
di = {
"h": {
"users": {
"535485026905882626": "Benji",
"702646374155419648": "rosesaredumb",
"578938036960624660": "invalid-user",
"360366991149891585": "Goodra999"
}
}
}
to_add = {
"leaders": {
"57893803696076560": "josh"
}
}
di["h"] = [di["h"], to_add]
print(di)
# Output:
# {
# 'h': [
# {
# 'users': {
# '535485026905882626': 'Benji',
# '702646374155419648': 'rosesaredumb',
# '578938036960624660': 'invalid-user',
# '360366991149891585': 'Goodra999'
# }
# },
# {
# 'leaders': {
# '57893803696076560': 'josh'
# }
# }
# ]
# }

Adding session attributes in Python for Alexa skills

I have 3 slots (account, dollar_value, recipient_first) within my intent schema for an Alexa skill and I want to save whatever slots are provided by the speaker in the session Attributes.
I am using the following methods to set session attributes:
def create_dollar_value_attribute(dollar_value):
return {"dollar_value": dollar_value}
def create_account_attribute(account):
return {"account": account}
def create_recipient_first_attribute(recipient_first):
return {"recipient_first": recipient_first}
However, as you may guess, if I want to save more than one slot as data in sessionAttributes, the sessionAttributes is overwritten as in the following case:
session_attributes = {}
if session.get('attributes', {}) and "recipient_first" not in session.get('attributes', {}):
recipient_first = intent['slots']['recipient_first']['value']
session_attributes = create_recipient_first_attribute(recipient_first)
if session.get('attributes', {}) and "dollar_value" not in session.get('attributes', {}):
dollar_value = intent['slots']['dollar_value']['value']
session_attributes = create_dollar_value_attribute(dollar_value)
The JSON response from my lambda function for a speech input in which two slots (dollar_value and recipient_first) were provided is as follows (my guess is that the create_dollar_value_attribute method in the second if statement is overwriting the first):
{
"version": "1.0",
"response": {
"outputSpeech": {
"type": "PlainText",
"text": "Some text output"
},
"card": {
"content": "SessionSpeechlet - Some text output",
"title": "SessionSpeechlet - Send Money",
"type": "Simple"
},
"reprompt": {
"outputSpeech": {
"type": "PlainText"
}
},
"shouldEndSession": false
},
"sessionAttributes": {
"dollar_value": "30"
}
}
The correct response for sessionAttributes should be:
"sessionAttributes": {
"dollar_value": "30",
"recipient_first": "Some Name"
},
How do I create this response? Is there a better way to add values to sessionAttributes in the JSON response?
The easiest way to add sessionAttributes with Python in my opinion seems to be by using a dictionary. For example, if you want to store some of the slots for future in the session attributes:
session['attributes']['slotKey'] = intent['slots']['slotKey']['value']
Next, you can just pass it on to the build response method:
buildResponse(session['attributes'], buildSpeechletResponse(title, output, reprompt, should_end_session))
The implementation in this case:
def buildSpeechletResponse(title, output, reprompt_text, should_end_session):
return {
'outputSpeech': {
'type': 'PlainText',
'text': output
},
'card': {
'type': 'Simple',
'title': "SessionSpeechlet - " + title,
'content': "SessionSpeechlet - " + output
},
'reprompt': {
'outputSpeech': {
'type': 'PlainText',
'text': reprompt_text
}
},
'shouldEndSession': should_end_session
}
def buildResponse(session_attributes, speechlet_response):
return {
'version': '1.0',
'sessionAttributes': session_attributes,
'response': speechlet_response
}
This creates the sessionAttributes in the recommended way in the Lambda response JSON.
Also just adding a new sessionAttribute doesn't overwrite the last one if it doesn't exist. It will just create a new key-value pair.
Do note, that this may work well in the service simulator but may return a key attribute error when testing on an actual Amazon Echo. According to this post,
On Service Simulator, sessions starts with Session:{ ... Attributes:{}, ... }
When sessions start on the Echo, Session does not have an Attributes key at all.
The way I worked around this was to just manually create it in the lambda handler whenever a new session is created:
if event['session']['new']:
event['session']['attributes'] = {}
onSessionStarted( {'requestId': event['request']['requestId'] }, event['session'])
if event['request']['type'] == 'IntentRequest':
return onIntent(event['request'], event['session'])
First, you have to define the session_attributes.
session_attributes = {}
Then instead of using
session_attributes = create_recipient_first_attribute(recipient_first)
You should use
session_attributes.update(create_recipient_first_attribute(recipient_first)).
The problem you are facing is because you are reassigning the session_attributes. Instead of this, you should just update the session_attributes.
So your final code will become:
session_attributes = {}
if session.get('attributes', {}) and "recipient_first" not in session.get('attributes', {}):
recipient_first = intent['slots']['recipient_first']['value']
session_attributes.update(create_recipient_first_attribute(recipient_first))
if session.get('attributes', {}) and "dollar_value" not in session.get('attributes', {}):
dollar_value = intent['slots']['dollar_value']['value']
session_attributes.update(create_dollar_value_attribute(dollar_value))
The ASK SDK for Python provides an attribute manager, to manage request/session/persistence level attributes in the skill. You can look at the color picker sample, to see how to use these attributes in skill development.
Take a look at the below:
account = intent['slots']['account']['value']
dollar_value = intent['slots']['dollar_value']['value']
recipient_first = intent['slots']['recipient_first']['value']
# put your data in a dictionary
attributes = {
'account':account,
'dollar_value':dollar_value,
'recipient_first':recipient_first
}
Put the attributes dictionary in 'sessionAttributes' in your response. You should get it back in 'sessionAttributes' once Alexa replies to you.
Hope this helps.
The following code snippet will also prevent overwriting the session attributes:
session_attributes = session.get('attributes', {})
if "recipient_first" not in session_attributes:
session_attributes['recipient_first'] = intent['slots']['recipient_first']['value']
if "dollar_value" not in session_attributes:
session_attributes['dollar_value'] = = intent['slots']['dollar_value']['value']

Why is code not executing after response received

I'm running the code below and it takes the user to PayPal to make a payment and then returns them to the return_url as expected. However the code doesn't execute any further and it doesn't execute the payment.
I have based my code on https://github.com/paypal/rest-api-sdk-python:
class PayPalHandler(tornado.web.RequestHandler):
def get(self):
logging.basicConfig(level=logging.INFO)
paypal.configure({
"mode": PAYPAL_MODE,
"client_id": PAYPAL_CLIENT_ID,
"client_secret": PAYPAL_CLIENT_SECRET})
payment = paypal.Payment({
"intent": "sale",
"payer": {
"payment_method": "paypal" },
"redirect_urls": {
"return_url": "http://127.0.0.1:8000/ty",
"cancel_url": "http://127.0.0.1:8000/" },
"transactions": [ {
"item_list": {
"items": [{
"name": "membership",
"price": "2.00",
"currency": "GBP",
"quantity": 1 }]},
"amount": {
"total": "2.00",
"currency": "GBP" },
"description": "One of membership fee." } ] } )
redirect_url = ""
if payment.create():
print("Payment[%s] created successfully"%(payment.id))
for link in payment.links:
if link.method == "REDIRECT":
redirect_url = link.href
print("Redirect for approval: %s"%(redirect_url))
return self.redirect(redirect_url)
else:
print("Error while creating payment.")
print(payment.error)
response = payment.to_dict()
print response
payment = paypal.Payment.find(payment.id)
if payment.execute({"payer_id": response['payer_id']}):
print ("Payment executed successfully")
else:
print(payment.error) # Error Hash
print payment.to_dict()
print userData
So in the example at https://devtools-paypal.com/guide/pay_paypal/python?success=true&token=EC-8JL96732FP068791F&PayerID=QQGSRNHDACTLJ. Step 5 is not happening and no response is sent from PayPal?
This is Avi from PayPal here. I am not super familiar with Tornado, but after the line return self.redirect(redirect_url) happens in your code, and returns the user to the return_url, in payment.execute({"payer_id": response['payer_id']}) are you getting the payer_id correctly? Payer_id is returned appended to the return_url as one of the parameters in the format http://<return_url>?token=EC-60U79048BN7719609&PayerID=7E7MGXCWTTKK2. Also, what is the status of the payment after you execute payment = paypal.Payment.find(payment.id). The other suggestion I would have is to see if print payment.error prints a useful debug message and a debug_id which paypal merchant technical services can use to look at the issue.
You need other url where papypal redirects when the payment had been successful, where you will receive the token and the PayerID. In that GET method, you can put this part of the code (pseudocode):
payerid_param = request.get('PayerID')
payment = paypal.Payment.find(db_payment.id)
if payment.execute({"payer_id": payerid_param}):
print ("Payment executed successfully")
else:
print(payment.error) # Error Hash
You will need to save the payment_id between calls.
Why you use
return self.redirect(redirect_url)
I think you can use just
self.redirect(redirect_url)
I've never seen return statement in Tornado handlers.

Categories