Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flask app runs in dev, doesn't work in apache mod_wsgi #87

Closed
fredzannarbor opened this issue May 7, 2019 · 13 comments
Closed

Flask app runs in dev, doesn't work in apache mod_wsgi #87

fredzannarbor opened this issue May 7, 2019 · 13 comments

Comments

@fredzannarbor
Copy link

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[ ] Feature request
[x] Documentation issue or request
[x ] Other... Please describe:

Expected Behavior

App basketball.py is working correctly on Alexa Dev Console > Test > ngrok > endpoint > local dev machine > Flask.

Current Behavior

Now I want to move same app to Apache mod_wsgi since Flask is not production ready. Unfortunately when I then call the app via the wsgi, it does not work and returns null to alexa.

Possible Solution

// Not required, but suggest a fix/reason for the bug,
// or ideas how to implement the addition or change

I suppose I could try to use the generic webservice but then a) I wouldn't be getting the benefits of Flask in dev and b) I don't know how to translate into the request formats for mod_wsgi.

Steps to Reproduce (for bugs)

// Provide a self-contained, concise snippet of code
// For more complex issues provide a repo with the smallest sample that reproduces the bug
// Including business logic or unrelated code makes diagnosis more difficult

basketball.wsgi:

#! /usr/bin/python3
import sys
import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='/home/ubuntu/voice/AltBrains_Basketball/altbrains-basketball.log', filemode='a+')
path = '/home/ubuntu/voice/AltBrains_Basketball/basketball.py'
if path not in sys.path:
   sys.path.insert(0, path)
logging.debug(sys.path)
logging.debug(sys.version)
import getpass
logging.debug(getpass.getuser())
from basketball import app as application
logging.debug(application)

The log shows that the endpoint triggers the wsgi (which means the apache conf is correct) and the wsgi tries to run the app, but fails returning null json.

Tue, 07 May 2019 00:23:12 DEBUG    ['/home/ubuntu/voice/AltBrains_Baske
tball/basketball.py', '/home/ubuntu/voice/AltBrains_Basketball', '/usr/
lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynloa
d', '/home/ubuntu/.local/lib/python3.6/site-packages', '/usr/local/lib/
python3.6/dist-packages', '/usr/lib/python3/dist-packages']
Tue, 07 May 2019 00:23:12 DEBUG    3.6.7 (default, Oct 22 2018, 11:32:1
7)
[GCC 8.2.0]
Tue, 07 May 2019 00:23:12 DEBUG    ubuntu
Tue, 07 May 2019 00:23:13 DEBUG    <Flask 'basketball'>

Context

Your Environment

  • ASK SDK for Python used: 1.9.0
  • Operating System and version: OS/x

Python version info

  • Python version used for development: 3.6.7
@nikhilym
Copy link
Contributor

nikhilym commented May 7, 2019

Hey @fredzannarbor , can you please provide more info on the input request received and if it is correctly being routed to the skill adapter code or is failing even before that?

@fredzannarbor
Copy link
Author

OK as follows.
current basketball.wsgi

#! /usr/bin/python3
import sys
import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='/home/ubuntu/voice/AltBrains_Basketball/out2222.log', filemode='w')
path = '/home/ubuntu/voice/AltBrains_Basketball/basketball.py'
if path not in sys.path:
   sys.path.insert(0, path)
logging.debug(sys.path)
logging.debug(sys.version)
import getpass
logging.debug(getpass.getuser())
from basketball import app as application
logging.debug(application)

current basketball.py with log statements before and after skill adapter

# -*- coding: utf-8 -*-
#! /usr/bin/python3


#import json
import logging
import random

from flask import Flask
from ask_sdk_core.skill_builder import SkillBuilder
from flask_ask_sdk.skill_adapter import SkillAdapter

from ask_sdk_core.utils import is_request_type, is_intent_name
from ask_sdk_core.handler_input import HandlerInput

from ask_sdk_model.ui import SimpleCard
from ask_sdk_model import Response, RequestEnvelope

app = Flask(__name__)

logging.debug('start of program')
sb = SkillBuilder()

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

type_of_shot = "type_of_shot"
logging.debug(type_of_shot)
# game-specific functions that are not responses to Intents

def setup_situation():
    # sets up the situation for the player
    player_team = ['Your team']
    relation = [' is behind by ', ' is down by ', ' trails by ']
    points_down = 1 # two, three ...
    pointspread = ['one']
    conjunction = [' with ']
    time_remaining = ['the clock winding down', 'time almost elapsed', 'the buzzer about to sound', 'the last possession']
    possession = ['Your ball. ', 'Over to you. ', 'Your possession. ']
    stage_of_season = [' championship game ', ' conference championship ', ' conference tournament']
    NBA_stages = [' game five'] * 3 + ['game six'] * 3 + [' game seven'] * 3
    overtime = [' regulation'] * 10 + [' the first half'] * 3 + [ ' overtime' ] * 2 + [' double overtime'] * 1 + [' triple overtime'] * 1
    special_circumstances = ['Homecoming']
    situation = random.choice(player_team) +  random.choice(relation)  + random.choice(pointspread) + random.choice(conjunction) + random.choice(time_remaining) + ' in ' + random.choice(overtime) + '. '
    print(situation)
    logging.debug('ahaha')
    return situation

def update_situation(myshot):
    points_down = 1
    raw_probability = 0.5
    contesteds = ['Embarrassingly open. '] * 3 + ['Wide open. '] * 5 + ['It is contested... '] * 20 + ['Fiercely contested. '] * 10
    contestedness = random.uniform(.8, 1.0)
    # distance_modifier
    adjusted_probability = raw_probability * contestedness
    this_try = random.random()

    if this_try < adjusted_probability:

        points_down = points_down - 2
        basket_made = 1
        call = random.choice(['Rattles round the rim and in. ', 'Swish! ', 'That is good. ', 'Good. ', 'Drained it. ', 'Basket. ', 'Friendly bounce! ', 'Nothing but net! '])
        compliment = random.choice(['That was clutch. ', 'Great shot. ', 'You da man. ', 'Awesome. ', 'Wow. ', 'Amazing. ', 'Wow. Just wow.'])
    else:

        points_down = points_down
        basket_made = 0
        call = random.choice(['Short. ', 'Just short. ', 'Air ball. ', 'Long. ', 'Way long. ', 'Rattles off the rim. ', 'Rattles round the rim and out. ' ])
        taunt = random.choice(['Ha ha. ', 'Choke. ', 'Tough luck. ', 'You suck. ', 'I win. ', 'I won. ', ' Go me. ', 'I have bragging rights. '])
    # report on how tough the shot was

    percentage_this_try = "{:.0%}".format(this_try)
    percentage_probability = "{:.0%}".format(adjusted_probability)
    print(percentage_this_try, percentage_probability, basket_made)


    if points_down < 0:
       outcome_description = call + random.choice(['For the win! ', 'You win! ']) + compliment
    if points_down > 0:
        outcome_description = call + random.choice(['You lose! ', 'That is an L. ']) + taunt

    print(outcome_description)

    return outcome_description


@sb.request_handler(can_handle_func=is_request_type("LaunchRequest"))
def launch_request_handler(handler_input):
    """Handler for Skill Launch."""
    # type: (HandlerInput) -> Response
    call_to_action = ['Call your shot. ', 'Your turn. ', 'Your shot. ', 'You go.', 'Say type of shot. ']
    speech_text ="Welcome to AltBrains Basketball. "  + setup_situation() + random.choice(call_to_action)

    return handler_input.response_builder.speak(speech_text).set_card(
        SimpleCard("Welcome to AltBrains Basketball", speech_text)).set_should_end_session(
        False).response

@sb.request_handler(can_handle_func=is_intent_name("TakeShotIntent"))
def take_shot_intent_handler(handler_input):
    """Handler for TakeShotIntent."""
    # type: (HandlerInput) -> Response

    slots = handler_input.request_envelope.request.intent.slots
    #myshot = slots['Item'].value
    myshot = slots["type_of_shot"].value
    print('myshot was ' + str(myshot))
    result = update_situation(myshot)
    pause = '<break time=\"3s\"/>'
    newgame = ['New game. ', 'Rematch. ', 'Play again? ', 'Lets play again. ' ]
    transition = ' Call your shot, or say goodbye. '
    intro = ['Your attempt was a ', 'You tried a ', ' ', 'That was a ']
    speech_text = random.choice(intro) + myshot + '. ' + result + pause +  random.choice(newgame) + setup_situation() + transition
    card_text = result + transition
    return handler_input.response_builder.speak(speech_text).set_card(
        SimpleCard("Your Shot", card_text)).set_should_end_session(False).response


@sb.request_handler(can_handle_func=is_intent_name("AMAZON.HelpIntent"))
def help_intent_handler(handler_input):
    """Handler for Help Intent."""
    # type: (HandlerInput) -> Response
    speech_text = "Call your shot. For example, say jump shot or sky hook."

    return handler_input.response_builder.speak(speech_text).ask(
        speech_text).set_card(SimpleCard(
            "Shot Help", speech_text)).response


@sb.request_handler(
    can_handle_func=lambda handler_input:
        is_intent_name("AMAZON.CancelIntent")(handler_input) or
        is_intent_name("AMAZON.StopIntent")(handler_input))
def cancel_and_stop_intent_handler(handler_input):
    """Single handler for Cancel and Stop Intent."""
    # type: (HandlerInput) -> Response
    speech_text = "Thanks for playing!"

    return handler_input.response_builder.speak(speech_text).set_card(
        SimpleCard("Thanks for playing!", speech_text)).response


@sb.request_handler(can_handle_func=is_intent_name("AMAZON.FallbackIntent"))
def fallback_handler(handler_input):
    """AMAZON.FallbackIntent is only available in en-US locale.
    This handler will not be triggered except in that locale,
    so it is safe to deploy on any locale.
    """
    # type: (HandlerInput) -> Response
    speech = (
        "I'm not sure what you want me to do.")
    reprompt = "You can call your shot by saying type of shot followed by distance. For example, jump shot from twenty feet."
    handler_input.response_builder.speak(speech).ask(reprompt)
    return handler_input.response_builder.response


@sb.request_handler(can_handle_func=is_request_type("SessionEndedRequest"))
def session_ended_request_handler(handler_input):
    """Handler for Session End."""
    # type: (HandlerInput) -> Response
    return handler_input.response_builder.response


@sb.exception_handler(can_handle_func=lambda i, e: True)
def all_exception_handler(handler_input, exception):
    """Catch all exception handler, log exception and
    respond with custom message.
    """
    # type: (HandlerInput, Exception) -> Response
    logger.error(exception, exc_info=True)

    speech = "Sorry, I couldn't understand your intent. Please try again!!"
    handler_input.response_builder.speak(speech).ask(speech)

    return handler_input.response_builder.response

logging.debug('before skill adapter')

skill_adapter = SkillAdapter(
    skill=sb.create(), skill_id="amzn1.ask.skill.5cbfc4d6-35dd-436f-9dbe-bab5dda42409", app=app)

logging.debug('after skill adapter')
logging.debug(skill_adapter)
skill_adapter.register(app=app, route="/")

if __name__ == '__main__':

    app.run()

log output shows that program is reaching points before and after skill adapter

Wed, 08 May 2019 15:37:45 DEBUG    ['/home/ubuntu/voice/AltBrains_Baske
tball/basketball.py', '/home/ubuntu/voice/AltBrains_Basketball', '/usr/
lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynloa
d', '/home/ubuntu/.local/lib/python3.6/site-packages', '/usr/local/lib/
python3.6/dist-packages', '/usr/lib/python3/dist-packages']
Wed, 08 May 2019 15:37:45 DEBUG    3.6.7 (default, Oct 22 2018, 11:32:1
7)
[GCC 8.2.0]
Wed, 08 May 2019 15:37:45 DEBUG    ubuntu
Wed, 08 May 2019 15:37:45 DEBUG    start of program
Wed, 08 May 2019 15:37:45 DEBUG    type_of_shot
Wed, 08 May 2019 15:37:45 DEBUG    before skill adapter
Wed, 08 May 2019 15:37:45 DEBUG    after skill adapter
Wed, 08 May 2019 15:37:45 DEBUG    <flask_ask_sdk.skill_adapter.SkillAd
apter object at 0x7f8fedaa7630>
Wed, 08 May 2019 15:37:45 DEBUG    <Flask 'basketball'>

json input from alexa development console

{
	"version": "1.0",
	"session": {
		"new": false,
		"sessionId": "amzn1.echo-api.session.13641c66-e21f-44ab-a290-c34d0129bef5",
		"application": {
			"applicationId": "amzn1.ask.skill.5cbfc4d6-35dd-436f-9dbe-bab5dda42409"
		},
		"user": {
			"userId": "amzn1.ask.account.AF5C23VUYVYCZB6U77NZDK7XBIZ3WC3KCC6AZSLTUSIL2HOK2HPOMDCFXTVS3BUD7KONNHXKLRMEKTV6F2HSTF5YTCMYAP6UENP6PSBRULQVDDUCMCJGCB2PWGUORTN2VVIFWDYINZJNRW4RDGWRF2BQ4RWPRDQHDG4YYRJFHLORCH3EAGUFSGQIE7LNEPMAWAOBGMMLF5L7ESA"
		}
	},
	"context": {
		"System": {
			"application": {
				"applicationId": "amzn1.ask.skill.5cbfc4d6-35dd-436f-9dbe-bab5dda42409"
			},
			"user": {
				"userId": "amzn1.ask.account.AF5C23VUYVYCZB6U77NZDK7XBIZ3WC3KCC6AZSLTUSIL2HOK2HPOMDCFXTVS3BUD7KONNHXKLRMEKTV6F2HSTF5YTCMYAP6UENP6PSBRULQVDDUCMCJGCB2PWGUORTN2VVIFWDYINZJNRW4RDGWRF2BQ4RWPRDQHDG4YYRJFHLORCH3EAGUFSGQIE7LNEPMAWAOBGMMLF5L7ESA"
			},
			"device": {
				"deviceId": "amzn1.ask.device.AHDOSOCQTNKPLXE2HTLPAUDSUDNWHXI2IIBO2P44LQFBHEMWGWCKUA52S3PEMULSNGTKEU3DDDUILPC44TQBTYBFXWBTESUSDAWFQD422LJTZU7JWNZXGMWTNDSEQGEZT5TF44WIW3A24PYG4XAIJT5TIQUSYWZW2NW7BS7S2LORAXSB5ZJK2",
				"supportedInterfaces": {}
			},
			"apiEndpoint": "https://api.amazonalexa.com",
			"apiAccessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJhdWQiOiJodHRwczovL2FwaS5hbWF6b25hbGV4YS5jb20iLCJpc3MiOiJBbGV4YVNraWxsS2l0Iiwic3ViIjoiYW16bjEuYXNrLnNraWxsLjVjYmZjNGQ2LTM1ZGQtNDM2Zi05ZGJlLWJhYjVkZGE0MjQwOSIsImV4cCI6MTU1NzMzMDE2NiwiaWF0IjoxNTU3MzI5ODY2LCJuYmYiOjE1NTczMjk4NjYsInByaXZhdGVDbGFpbXMiOnsiY29uc2VudFRva2VuIjpudWxsLCJkZXZpY2VJZCI6ImFtem4xLmFzay5kZXZpY2UuQUhET1NPQ1FUTktQTFhFMkhUTFBBVURTVUROV0hYSTJJSUJPMlA0NExRRkJIRU1XR1dDS1VBNTJTM1BFTVVMU05HVEtFVTNERERVSUxQQzQ0VFFCVFlCRlhXQlRFU1VTREFXRlFENDIyTEpUWlU3SldOWlhHTVdUTkRTRVFHRVpUNVRGNDRXSVczQTI0UFlHNFhBSUpUNVRJUVVTWVdaVzJOVzdCUzdTMkxPUkFYU0I1WkpLMiIsInVzZXJJZCI6ImFtem4xLmFzay5hY2NvdW50LkFGNUMyM1ZVWVZZQ1pCNlU3N05aREs3WEJJWjNXQzNLQ0M2QVpTTFRVU0lMMkhPSzJIUE9NRENGWFRWUzNCVUQ3S09OTkhYS0xSTUVLVFY2RjJIU1RGNVlUQ01ZQVA2VUVOUDZQU0JSVUxRVkREVUNNQ0pHQ0IyUFdHVU9SVE4yVlZJRldEWUlOWkpOUlc0UkRHV1JGMkJRNFJXUFJEUUhERzRZWVJKRkhMT1JDSDNFQUdVRlNHUUlFN0xORVBNQVdBT0JHTU1MRjVMN0VTQSJ9fQ.EIf8KVnc6cpKJnKLa_ERF3Nx0QLDqXcx9y0sfiOcAwepoEDyg-xhdtDTAFRTxY3zqsQhNaXd0zgzcsDyjqmTj_Fea6C3WMht3TTraTK23y3TE7WIUE1LhZSZj-w2q2YHrFTJs-O4nMXU5k5cp-toolvTD4TjAZwEf_uPWNeSnVhIWSOGQ7fA-UVYUoLvBG9fyAgleM5RVjKY9iTqKN9G-livF_38O8sAIe5-hrda6NU90XFRtRzZ4ZgiV3eNx6Bw-1AhYsRfKOcSxfKx0Za2r6T-jejhMo1FtqHfeuvk3NqqJyTujxhC-qhsWDuiPagfwkDmSMWs43OZ0eCPMOnlmg"
		},
		"Viewport": {
			"experiences": [
				{
					"arcMinuteWidth": 246,
					"arcMinuteHeight": 144,
					"canRotate": false,
					"canResize": false
				}
			],
			"shape": "RECTANGLE",
			"pixelWidth": 1024,
			"pixelHeight": 600,
			"dpi": 160,
			"currentPixelWidth": 1024,
			"currentPixelHeight": 600,
			"touch": [
				"SINGLE"
			],
			"video": {
				"codecs": [
					"H_264_42",
					"H_264_41"
				]
			}
		}
	},
	"request": {
		"type": "SessionEndedRequest",
		"requestId": "amzn1.echo-api.request.6ec39b05-fcef-4882-8be1-83b6c880bd6f",
		"timestamp": "2019-05-08T15:37:46Z",
		"locale": "en-US",
		"reason": "ERROR",
		"error": {
			"type": "INVALID_RESPONSE",
			"message": "**An exception occurred while dispatching the request to the skill."
		}**
	}
}

The same script runs fine when I access it via ngrok > flask on local dev.

I

@fredzannarbor
Copy link
Author

I managed to do some introspection on the skill_adapter object:

logging.debug(skill_adapter.__dict__)

Thu, 09 May 2019 00:36:08 DEBUG    before skill adapter
Thu, 09 May 2019 00:36:08 DEBUG    {'_skill_id': 'amzn1.ask.skill.5cbfc4d6-35dd-436f-9dbe-bab5dda42409', '_skill': <ask_sdk_core.s
kill.CustomSkill object at 0x7f8fedaa64a8>, '_webservice_handler': <ask_sdk_webservice_support.webservice_handler.WebserviceSkillH
andler object at 0x7f8fedaa65f8>, '_verifiers': []}
Thu, 09 May 2019 00:36:08 DEBUG    after skill adapter

Does this look right? I'm getting more puzzled because it sure looks like the basketball flask ask is running.

@nikhilym
Copy link
Contributor

nikhilym commented May 9, 2019

Hey @fredzannarbor , the _verifiers under the webservice handler should be initialized to an empty list, but the corresponding request and timestamp verifiers are added later. I don't think that would result in an invalid response json as output, considering it is working on the local dev instance. Also the json input through the dev console seems wrong. Can you add a response interceptor to log the response from the skill, to see if the skill invocation is working as expected?

Also, did you remove the app.run() command from __main__ call as suggested in the flask docs, when trying to run through mod_wsgi?

@fredzannarbor
Copy link
Author

I removed the app.run/main text from basketball.py

if __name__ == '__main__':
    app.run()

that didn't make any difference.

I tried adding a response interceptor before the skill adapter (was that the right place?) but I don't see any output anywhere.

# -*- coding: utf-8 -*-
#! /usr/bin/python3

#import json
import logging
import random
import inspect

from flask import Flask
from ask_sdk_core.skill_builder import SkillBuilder
from flask_ask_sdk.skill_adapter import SkillAdapter

from ask_sdk_core.utils import is_request_type, is_intent_name
from ask_sdk_core.handler_input import HandlerInput

from ask_sdk_model.ui import SimpleCard
from ask_sdk_model import Response, RequestEnvelope

app = Flask(__name__)


logging.debug('start of program')

sb = SkillBuilder()
logging.debug(sb.__dict__)

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

type_of_shot = "type_of_shot"
logging.debug(type_of_shot)
# game-specific functions that are not responses to Intents

def setup_situation():
    # sets up the situation for the player
    player_team = ['Your team']
    relation = [' is behind by ', ' is down by ', ' trails by ']
    points_down = 1 # two, three ...
    pointspread = ['one']
    conjunction = [' with ']
    time_remaining = ['the clock winding down', 'time almost elapsed', 'the buzzer about to sound', 'the last possession']
    possession = ['Your ball. ', 'Over to you. ', 'Your possession. ']
    stage_of_season = [' championship game ', ' conference championship ', ' conference tournament']
    NBA_stages = [' game five'] * 3 + ['game six'] * 3 + [' game seven'] * 3
    overtime = [' regulation'] * 10 + [' the first half'] * 3 + [ ' overtime' ] * 2 + [' double overtime'] * 1 + [' triple overtime'] * 1
    special_circumstances = ['Homecoming']
    situation = random.choice(player_team) +  random.choice(relation)  + random.choice(pointspread) + random.choice(conjunction) + random.choice(time_remaining) + ' in ' + random.choice(overtime) + '. '
    print(situation)
    logging.debug('ahaha')
    return situation

def update_situation(myshot):
    points_down = 1
    raw_probability = 0.5
    contesteds = ['Embarrassingly open. '] * 3 + ['Wide open. '] * 5 + ['It is contested... '] * 20 + ['Fiercely contested. '] * 10
    contestedness = random.uniform(.8, 1.0)
    # distance_modifier
    adjusted_probability = raw_probability * contestedness
    this_try = random.random()

    if this_try < adjusted_probability:

        points_down = points_down - 2
        basket_made = 1
        call = random.choice(['Rattles round the rim and in. ', 'Swish! ', 'That is good. ', 'Good. ', 'Drained it. ', 'Basket. ', 'Friendly bounce! ', 'Nothing but net! '])
        compliment = random.choice(['That was clutch. ', 'Great shot. ', 'You da man. ', 'Awesome. ', 'Wow. ', 'Amazing. ', 'Wow. Just wow.'])
    else:

        points_down = points_down
        basket_made = 0
        call = random.choice(['Short. ', 'Just short. ', 'Air ball. ', 'Long. ', 'Way long. ', 'Rattles off the rim. ', 'Rattles round the rim and out. ' ])
        taunt = random.choice(['Ha ha. ', 'Choke. ', 'Tough luck. ', 'You suck. ', 'I win. ', 'I won. ', ' Go me. ', 'I have bragging rights. '])
    # report on how tough the shot was

    percentage_this_try = "{:.0%}".format(this_try)
    percentage_probability = "{:.0%}".format(adjusted_probability)
    print(percentage_this_try, percentage_probability, basket_made)


    if points_down < 0:
       outcome_description = call + random.choice(['For the win! ', 'You win! ']) + compliment
    if points_down > 0:
        outcome_description = call + random.choice(['You lose! ', 'That is an L. ']) + taunt

    print(outcome_description)

    return outcome_description


@sb.request_handler(can_handle_func=is_request_type("LaunchRequest"))
def launch_request_handler(handler_input):
    """Handler for Skill Launch."""
    # type: (HandlerInput) -> Response
    call_to_action = ['Call your shot. ', 'Your turn. ', 'Your shot. ', 'You go.', 'Say type of shot. ']
    speech_text ="Welcome to AltBrains Basketball. "  + setup_situation() + random.choice(call_to_action)

    return handler_input.response_builder.speak(speech_text).set_card(
        SimpleCard("Welcome to AltBrains Basketball", speech_text)).set_should_end_session(
        False).response

@sb.request_handler(can_handle_func=is_intent_name("TakeShotIntent"))
def take_shot_intent_handler(handler_input):
    """Handler for TakeShotIntent."""
    # type: (HandlerInput) -> Response

    slots = handler_input.request_envelope.request.intent.slots
    #myshot = slots['Item'].value
    myshot = slots["type_of_shot"].value
    print('myshot was ' + str(myshot))
    result = update_situation(myshot)
    pause = '<break time=\"3s\"/>'
    newgame = ['New game. ', 'Rematch. ', 'Play again? ', 'Lets play again. ' ]
    transition = ' Call your shot, or say goodbye. '
    intro = ['Your attempt was a ', 'You tried a ', ' ', 'That was a ']
    speech_text = random.choice(intro) + myshot + '. ' + result + pause +  random.choice(newgame) + setup_situation() + transition
    card_text = result + transition
    return handler_input.response_builder.speak(speech_text).set_card(
        SimpleCard("Your Shot", card_text)).set_should_end_session(False).response


@sb.request_handler(can_handle_func=is_intent_name("AMAZON.HelpIntent"))
def help_intent_handler(handler_input):
    """Handler for Help Intent."""
    # type: (HandlerInput) -> Response
    speech_text = "Call your shot. For example, say jump shot or sky hook."

    return handler_input.response_builder.speak(speech_text).ask(
        speech_text).set_card(SimpleCard(
            "Shot Help", speech_text)).response


@sb.request_handler(
    can_handle_func=lambda handler_input:
        is_intent_name("AMAZON.CancelIntent")(handler_input) or
        is_intent_name("AMAZON.StopIntent")(handler_input))
def cancel_and_stop_intent_handler(handler_input):
    """Single handler for Cancel and Stop Intent."""
    # type: (HandlerInput) -> Response
    speech_text = "Thanks for playing!"

    return handler_input.response_builder.speak(speech_text).set_card(
        SimpleCard("Thanks for playing!", speech_text)).response


@sb.request_handler(can_handle_func=is_intent_name("AMAZON.FallbackIntent"))
def fallback_handler(handler_input):
    """AMAZON.FallbackIntent is only available in en-US locale.
    This handler will not be triggered except in that locale,
    so it is safe to deploy on any locale.
    """
    # type: (HandlerInput) -> Response
    speech = (
        "I'm not sure what you want me to do.")
    reprompt = "You can call your shot by saying type of shot followed by distance. For example, jump shot from twenty feet."
    handler_input.response_builder.speak(speech).ask(reprompt)
    return handler_input.response_builder.response


@sb.request_handler(can_handle_func=is_request_type("SessionEndedRequest"))
def session_ended_request_handler(handler_input):
    """Handler for Session End."""
    # type: (HandlerInput) -> Response
    return handler_input.response_builder.response


@sb.exception_handler(can_handle_func=lambda i, e: True)
def all_exception_handler(handler_input, exception):
    """Catch all exception handler, log exception and
    respond with custom message.
    """
    # type: (HandlerInput, Exception) -> Response
    logger.error(exception, exc_info=True)

    speech = "Sorry, I couldn't understand your intent. Please try again!!"
    handler_input.response_builder.speak(speech).ask(speech)

    return handler_input.response_builder.response

logging.debug('before skill adapter')

**@sb.global_response_interceptor()
def response_logger(handler_input, response):
    print("Response generated: {}".format(response))
    logging.debug("Response generated: {}".format(response))**
    
skill_adapter = SkillAdapter(
    skill=sb.create(), skill_id="amzn1.ask.skill.5cbfc4d6-35dd-436f-9dbe-bab5dda42409", app=app)

logging.debug(skill_adapter.__dict__)
#logging.debug(inspect.getmembers(skill_adapter))
logging.debug('after skill adapter')
logging.debug(skill_adapter)
skill_adapter.register(app=app, route="/")

@nikhilym
Copy link
Contributor

Hey @fredzannarbor , I took over the skill code and set it up on apache mod_wsgi. Tested the endpoint using Postman, and skill dispatching is working as expected. I tried this for a launch request with only TimestampVerifier set as True at the moment and could see that the skill dispatch is happening as expected and the response is generated as needed.

Also, by setting the RequestVerifier as True, the skill adapter seems to be receiving the request since it checks the request validation and fails. Once the request certificate is configured through the developer console, the request verification also worked as expected.

Can you check if the request envelope is reaching route correctly? I am guessing there might be an issue with configuring the flask app through mod_wsgi, which is causing the issue.

@nikhilym
Copy link
Contributor

Hey @fredzannarbor , were you able to get this working?

@fredzannarbor
Copy link
Author

No, I'm still stuck. I created a separate Flask hello world app (not using ASK at all) that runs via mod_wsgi and performs correctly at https://www.altbrains.com/flasktest. The mod_wsgi log output for each program (basketball.wsgi and flasktest.wsgi) looks the same until the very end when the basketball app's log ends with a 308 PERMANENT REDIRECT instead of 200 for the flasktest log. I don't see why the redirect is being issued.

I am wondering about the skill_adapter.register(app=app, route="/") whether it is assuming some sort of default value. I am running apache off a bitnami stack /opt/lampstack... rather than off /var/www/apache.

I don't know how to answer your question about the request envelope reaching the route -- how would I double-check that?

Archive.zip

@fredzannarbor
Copy link
Author

PS I downloaded Postman but I don't know how to manually construct the POST commands to simulate Alexa -- is that available somewhere? I

@nikhilym
Copy link
Contributor

Hey @fredzannarbor , sorry to hear that. I am not sure what is leading to HTTP 308 error. Maybe some more debugging and investigating needs to be done.

I am wondering about the skill_adapter.register(app=app, route="/") whether it is assuming some sort of default value. I am running apache off a bitnami stack /opt/lampstack... rather than off /var/www/apache.

The register function just adds a URL rule similar to route decorator and is not defaulting anything on the app.

I don't know how to answer your question about the request envelope reaching the route -- how would I double-check that?

An easy way to check that would be to add a Request Interceptor and print the incoming request, to see if the request is passing to the skill or failing even before that. Considering you are not getting any log on Request Verification or Timestamp Verification failures (considering you didn't configure to disable these verifications) , either the request is not reaching the skill handlers at all or it is performing the routing right but somehow not handling the response output correctly.

Archive.zip

My sample skill wsgi file remains something similar, however I add the virtual env path as required by mod_wsgi, to pick up dependencies.

activate_this = '/my_envs/flask_env_virtual/bin/activate_this.py'
with open(activate_this) as file_:
    exec(file_.read(), dict(__file__=activate_this))

import sys
sys.path.insert(0, '/sample_project/flask_skill/')

from skill import app as application

PS I downloaded Postman but I don't know how to manually construct the POST commands to simulate Alexa -- is that available somewhere?

Unfortunately, I don't think there is any documentation at the moment. I generally set the following for testing :

  • HTTP method : POST
  • Authorization : Inherit auth from parent
  • Headers : application/json as content-type. The certificate information that is configured on the endpoint in the alexa developer console is not easy to get. So you can set the request verification to be False for postman testing.
  • Body: copy the request body from alexa developer console

This should generally be good enough for testing the request dispatching and output generation.

@fredzannarbor
Copy link
Author

I added a request interceptor before all the request handlers. It works via ngrok/Flask alone and correctly reports the request text, but does not find any request when Flask is triggered via mod_wsgi. What is happening seems very similar to this: pallets/flask#2229 a known issue with werkzeug's handling of chunked/multipart requests which flask/werkzeug has (lamely) decided not to fix pallets/flask#2547. However mod_wsgi does appear to be setting wsgi.input_terminated to true, which is the recommended workaround, but that alone doesn't fix the problem for me, so I'm still stuck.

At this point I am going to pause and see if I can get gunicorn working with the basketball app. As a side note, the reason I went with mod_wsgi rather than gunicorn was because I expect to have multiple Alexa apps soon, and I didn't want to have to figure out how to have multiple gunicorn endpoints off 443.

@fredzannarbor
Copy link
Author

Got things working in production using Apache proxy => gunicorn => flask app, with gunicorn launched by systemd in Ubuntu. Very good article here. https://www.vioan.eu/blog/2016/10/10/deploy-your-flask-python-app-on-ubuntu-with-apache-gunicorn-and-systemd/

@nikhilym
Copy link
Contributor

Thanks for the update @fredzannarbor . Glad you got it working.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants