Retain Session Data After Session Is Closed Or Store User Data In Alexa Skill Using Python In AWS DynamoDB- Persistent Attributes In Alexa

In order to make your Alexa skill more interactive it is necessary to save user information to make the skill smarter. An ideal example would be to store the score the user got after his / her game. You can use this information to let the user know his personal best. In this blog, I am going to show you exactly how to do this.

Photo by Anete Lusina

In my previous blog, I have spoken about how to store the session attribute and its uses. Take a look at it here if you are not aware of session attributes.

The ask-sdk provided by Amazon for python allows us to store the persistent attributes in AWS dynamodb as well as S3. In this blog, we are going to store it in AWS dynamoDB.

create a dynamodb adapter using the following code snippet. Enter the table name appropriately.

ddb_table_name = "table-name"
ddb_resource = boto3.resource('dynamodb')
dynamodb_adapter = DynamoDbAdapter(table_name=ddb_table_name, create_table=True, dynamodb_resource=ddb_resource)

Instantiate CustomSkillBuilder() as follows.

sb = CustomSkillBuilder(persistence_adapter = dynamodb_adapter)

Now the initial setup is completed. It is time to save data into dynamodb.

persistent_attributes = handler_input.attributes_manager.persistent_attributes
persistent_attributes['source'] = "ranjithkumarmadhavan.com"
handler_input.attributes_manager.save_persistent_attributes()

Now that we know how to save data into dynamodb. let us retrieve data from it.

persistent_attributes = handler_input.attributes_manager.persistent_attributes
source = persistent_attributes['source']

The following code snippet will delete all the records from dynamodb for that user

handler_input.attributes_manager.delete_persistent_attributes()

Simple! Isn’t it? Now you can maintain user data even after the session is closed.

The following is a simple hello world python program.

import logging
import ask_sdk_core.utils as ask_utils
import random
from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.dispatch_components import AbstractExceptionHandler
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model.ui import SimpleCard
from ask_sdk_dynamodb.adapter import DynamoDbAdapter
from ask_sdk_core.skill_builder import CustomSkillBuilder
import json
import boto3

from ask_sdk_model import Response

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# Defining the database region, table name and dynamodb persistence adapter
ddb_table_name = "table-name"
ddb_resource = boto3.resource('dynamodb')
dynamodb_adapter = DynamoDbAdapter(table_name=ddb_table_name, create_table=True, dynamodb_resource=ddb_resource)

class LaunchRequestHandler(AbstractRequestHandler):
    """Handler for Skill Launch."""
    def can_handle(self, handler_input):

        return ask_utils.is_request_type("LaunchRequest")(handler_input) or ask_utils.is_intent_name("RestartIntent")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        # target = random.randint(1,20)
        speak_output = "Hello. Welcome back. "
        # type: (HandlerInput) -> bool
        persistent_attributes = handler_input.attributes_manager.persistent_attributes
        # print(persistent_attributes)
        # handler_input.attributes_manager.save_persistent_attributes()

        # Delete all attributes from the DB
        # handler_input.attributes_manager.delete_persistent_attributes()
        return (
            handler_input.response_builder
                .speak(speak_output)
                .ask(speak_output)
                # .set_card(SimpleCard(card_title, speak_output))
                .response
        )


class HelloWorldIntentHandler(AbstractRequestHandler):
    """Handler for Hello World Intent."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return ask_utils.is_intent_name("HelloWorldIntent")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speak_output = "Hello World from Chennai!"
        slots = handler_input.request_envelope.request.intent.slots

        return (
            handler_input.response_builder
                .speak(speak_output)
                # .ask("add a reprompt if you want to keep the session open for the user to respond")
                # .set_should_end_session(True)
                .response
        )

class HelpIntentHandler(AbstractRequestHandler):
    """Handler for Help Intent."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return ask_utils.is_intent_name("AMAZON.HelpIntent")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speak_output = "you can say hello"

        return (
            handler_input.response_builder
                .speak(speak_output)
                .ask(speak_output)
                .response
        )


class CancelOrStopIntentHandler(AbstractRequestHandler):
    """Single handler for Cancel and Stop Intent."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return (ask_utils.is_intent_name("AMAZON.CancelIntent")(handler_input) or
                ask_utils.is_intent_name("AMAZON.StopIntent")(handler_input))

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speak_output = "Goodbye!"

        return (
            handler_input.response_builder
                .speak(speak_output)
                .response
        )


class SessionEndedRequestHandler(AbstractRequestHandler):
    """Handler for Session End."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return ask_utils.is_request_type("SessionEndedRequest")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response

        # Any cleanup logic goes here.

        return handler_input.response_builder.response


class IntentReflectorHandler(AbstractRequestHandler):
    """The intent reflector is used for interaction model testing and debugging.
    It will simply repeat the intent the user said. You can create custom handlers
    for your intents by defining them above, then also adding them to the request
    handler chain below.
    """
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return ask_utils.is_request_type("IntentRequest")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        intent_name = ask_utils.get_intent_name(handler_input)
        speak_output = "You just triggered " + intent_name + "."

        return (
            handler_input.response_builder
                .speak(speak_output)
                # .ask("add a reprompt if you want to keep the session open for the user to respond")
                .response
        )


class CatchAllExceptionHandler(AbstractExceptionHandler):
    """Generic error handling to capture any syntax or routing errors. If you receive an error
    stating the request handler chain is not found, you have not implemented a handler for
    the intent being invoked or included it in the skill builder below.
    """
    def can_handle(self, handler_input, exception):
        # type: (HandlerInput, Exception) -> bool
        return True

    def handle(self, handler_input, exception):
        # type: (HandlerInput, Exception) -> Response
        logger.error(exception, exc_info=True)

        speak_output = "Sorry, I had trouble doing what you asked. Please try again."

        return (
            handler_input.response_builder
                .speak(speak_output)
                .ask(speak_output)
                .response
        )

# The SkillBuilder object acts as the entry point for your skill, routing all request and response
# payloads to the handlers above. Make sure any new handlers or interceptors you've
# defined are included below. The order matters - they're processed top to bottom.


sb = CustomSkillBuilder(persistence_adapter = dynamodb_adapter)

sb.add_request_handler(LaunchRequestHandler())
sb.add_request_handler(HelloWorldIntentHandler())
sb.add_request_handler(HelpIntentHandler())
sb.add_request_handler(CancelOrStopIntentHandler())
sb.add_request_handler(SessionEndedRequestHandler())
# sb.add_request_handler(IntentReflectorHandler()) # make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers

sb.add_exception_handler(CatchAllExceptionHandler())

lambda_handler = sb.lambda_handler()

To know more about session attributes visit the official documentation from Amazon.

Feel free to leave a comment down here. I would love to hear from you. Happy programming!!

Leave a Comment