alexa/alexa-apis-for-python

'NoneType' object has no attribute 'system'"

Daniyaldehleh opened this issue · 34 comments

Hi All!
I am trying to retrieve access token by calling handler_input.request_envelope.context.system.user.access_token , however; I am getting


{
  "errorMessage": "'NoneType' object has no attribute 'system'",
  "errorType": "AttributeError",
  "stackTrace": [
    "  File \"/opt/python/ask_sdk_core/skill_builder.py\", line 111, in wrapper\n    response_envelope = skill.invoke(\n",
    "  File \"/opt/python/ask_sdk_core/skill.py\", line 181, in invoke\n    api_token = request_envelope.context.system.api_access_token\n"
  ]
}

class GoogleIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        return ask_utils.is_intent_name("GoogleIntent")(handler_input)
    
    def handle(self, handler_input):
        #LOP = request.Session.User.AccessToken
        accessToken = handler_input.request_envelope.context.system.user.access_token

        global service
        service = build('calendar', 'v3', credentials=accessToken) 
        
        event = {
        'summary': 'Test',
        'location': 'At home',
        'description': 'A chance to hear more about Google\'s developer products.',
        'start': {
            'dateTime': '2020-07-29T12:12:10',
            'timeZone': 'America/Los_Angeles',
        },
        'end': {
            'dateTime': '2020-07-29T12:32:47',
            'timeZone': 'America/Los_Angeles',
        }
        }
        #check if you can call a color via the api too
        event = service.events().insert(calendarId='primary', body=event).execute()
        
        return (
            handler_input.response_builder
            .speak(speak_output)
            
        )    

I believe it's a capital "S" in "System".
handler_input.request_envelope.context.System.user.access_token

@timothyaaron I did try changing it to capital S. Unfortunately, nothing happened.

Can you elaborate on "nothing happened"? 🙂

Sorry, I just saved it again and test it and got:
"Input should be a RequestHandler instance". Looks like we are making progress :)

🤔 …that seems… odd. Are you sure it didn't say "'NoneType' object has no attribute 'System'"?

@timothyaaron exactly! it seemed odd! cause it should have said "System". Fortunately, after the 2nd save&test it got updated.

@timothyaaron Do you know what is meant by that error?

I haven't ever gotten that error. It would be helpful if you knew what line that error was generated on, and if you could provide it, but…

…if I had to guess I'd say the can_handle or handle function (or, even more likely, something further up the chain) isn't being passed a valid Request. 🤷‍♂️

Except that error appears to be generated after your access_token line. 🤔

And the only SDK thing you interact with following that is your return, so… wait… where do you define speak_output?

Oh. Got it.

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

@timothyaaron Thanks for looking into it. I pasted your code, however, this time after multiple save&test, I was faced with the same error. Input should be a RequestHandler instance

That was definitely an error, so keep that change.

I did a little digging. That error is returned in what's probably named lambda_function.py or similar (your entry point) on your .add_request_handler() line. The object being passed in isn't inheriting AbstractRequestHandler (which GoogleIntentHandler appears to be doing… so maybe a different one?).

Or maybe you're not instantiating it (make sure the () comes after GoogleIntentHandler).

i.e., sb.add_request_handler(GoogleIntentHandler())

@timothyaaron Thanks for looking it up.

I realized that I am actually stuck with the

 {
  "errorMessage": "'NoneType' object has no attribute 'system'",
  "errorType": "AttributeError",
  "stackTrace": [
    "  File \"/opt/python/ask_sdk_core/skill_builder.py\", line 111, in wrapper\n    response_envelope = skill.invoke(\n",
    "  File \"/opt/python/ask_sdk_core/skill.py\", line 181, in invoke\n    api_token = request_envelope.context.system.api_access_token\n"
  ]
}

and the reason it bypassed it last time and was giving that error was because it faced a new error after adding a new class. Thus, once I removed the class, I realize the "'NoneType' object has no attribute 'system'" error didn't actually go. Could it be due to an internal error? cause it is referring to skill_builder.py & skill.py file and not my lambda_function.py file.

Update: I posted the error on SO and look like, it is an internal error with the ask_sdk_core

A couple notes...

  1. I was wrong. The raw JSON uses System, the SDK uses system.
  2. I think you misunderstood the SO comment if you assumed it was an SDK error. Specifically, "That suggests you're using the library incorrectly and/or passing bad data somewhere".
  3. You are correct in that somehow request_envelope.context is a NoneType. That's getting build directly from your incoming event. context is a required object, so it should be there. Can you post your entire incoming request?

@danieldhz - it might not solve the problem you're having, but since you're using one util function already, I thought I'd point out there is one that gets the access token - get_account_linking_access_token

Depending on the code path, it might be that the object being passed into the handler is not the proper type. I've been able to work around that using something like this: request_envelope['context'] instead of the attribute notation.

I ran into a similar problem with caching product details as described in this blog. You'd have to adapt it to the request_envelope or whichever variable is the trouble spot.

@timothyaaron thanks for your constructive reply :)
I read the "..library incorrectly and/or passing bad data somewhere". But the thing is I am not even using any special libs in the class nor in the whole app to give such error. In fact, I have literally copy-pasted the same code to dev console, and it worked completely fine even after calling the GoogleIntentHandler. However, unfortunately, I can't use the dev console because I have to use external libs. Thus, I am completely confused on what's going on :(

@franklin-lobb thanks for pointing out. I will definitely keep that in mind in case we get there. However, even when I completely comment out the access token class, I am still left with the same error. On the other hand, when I paste the same code to the dev console it works completely fine even with access token class being on. Thus, lambda makes sense to be at fault as it's the only difference. However, I am not sure how I shall tweak lambda as I literally installed the libs and uploaded to lambda's layers.

by dev console, do you mean the Lambda console, or the Alexa console?

I experienced a situation previously (it was a while ago, and I don't recall if it was the same issue around the deserialization), however I could get the code to run in the Lambda console with a test event, but when I invoked the function via the source event, it wouldn't run, but modifying from the attribute notation (.context) to key notation (['context']) allowed it to work (and then I don't think it worked in the console).

@franklin-lobb Sorry, I meant Alexa dev console. The same code works in alexa dev console but not lambda. The thing is the request_envelope is already commented out and I am getting the same error. I tried adding

google-api-core==1.22.0
google-api-python-client==1.10.0
google-auth==1.19.2
google-auth-httplib2==0.0.4
google-auth-oauthlib==0.4.1
googleapis-common-protos==1.52.0

to the requirement.txt to import the packages into dev console, however; I got:
The 'google-api-core' distribution was not found and is required by the application Traceback

Hey @danieldhz , how are you testing the code? If you are using the test console on AWS Lambda, what is your request envelope JSON? If you are testing it through invoking the skill/intent, can you paste in the exact request envelope JSON that is being passed?

Is anyone else amused by their "helper functions"?
ask_sdk_core.utils.request_util.get_account_linking_access_token(handler_input)
handler_input.request_envelope.context.system.user.access_token

It literally just returns that line. You should already know what's in the request object. It's shorter and more explicit to just get what you need from where it actually exists in most cases vs memorizing longer "helper functions" that have to be imported.

Anyways… 🤔 …that's weird about running in the console. Now it's starting to sound like your code isn't being deployed properly. Do you get the error if you run it locally? (If you're comfortable sharing—repo>settings>manage_access>invite, I'm happy to spend a couple minutes digging into it.)

Hi Guys!
Thanks for all of your participation to make this work :)
As you guys mentioned, I realized that I wasn't testing the right JSON input. Therefore, I am really sorry for my testing immaturity. If I didn't have you I would've been scratching my head all day. Hopefully, one day I will be as good as you guys. Nevertheless, after testing with the right JSON, I realized the actual error is:

Input:


{
	"version": "1.0",
	"session": {
		"new": false,
		"sessionId": "amzn1.echo-api.session.ae83d982-4043-4371-a9d9-9aa39f85d79d",
		"application": {
			"applicationId": "amzn1.ask.skill.a8bac98c-dfbd-4caa-ae48-42e4cdad4f4e"
		},
		"user": {
			"userId": "amzn1.ask.account...",
			"permissions": {
				"consentToken": "eyJ0eXAiOiJKV..."
			}
		}
	},
	"context": {
		"Viewports": [
			{
				"type": "APL",
				"id": "main",
				"shape": "RECTANGLE",
				"dpi": 160,
				"presentationType": "STANDARD",
				"canRotate": false,
				"configuration": {
					"current": {
						"video": {
							"codecs": [
								"H_264_42",
								"H_264_41"
							]
						},
						"size": {
							"type": "DISCRETE",
							"pixelWidth": 1024,
							"pixelHeight": 600
						}
					}
				}
			}
		],
		"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"
				]
			}
		},
		"System": {
			"application": {
				"applicationId": "amzn1.ask.skill.a8bac98c-dfbd-4caa-ae48-42e4cdad"
			},
			"user": {
				"userId": "amzn1.ask.accou..",
				"permissions": {
					"consentToken": "eyJ0eXAiOiJKV1QiL.."
				}
			},
			"device": {
				"deviceId": "amzn1.ask.device.AGAN7ZCSHC7..",
				"supportedInterfaces": {}
			},
			"apiEndpoint": "https://api.amazonalexa.com",
			"apiAccessToken": "eyJ0e."
		}
	},
	"request": {
		"type": "IntentRequest",
		"requestId": "amzn1.echo-api.request.9caeb4cd-6ff5-4115-9d41-a4a87f",
		"locale": "en-US",
		"timestamp": "2020-08-11T15:14:11Z",
		"intent": {
			"name": "GoogleIntent",
			"confirmationStatus": "NONE"
		}
	}
}

Output:

{
  "version": "1.0",
  "sessionAttributes": {},
  "userAgent": "ask-python/1.14.0 Python/3.8.4",
  "response": {
    "outputSpeech": {
      "type": "SSML",
      "ssml": "<speak>Sorry, I had trouble doing what you asked. Please try again.</speak>"
    },
    "reprompt": {
      "outputSpeech": {
        "type": "SSML",
        "ssml": "<speak>Sorry, I had trouble doing what you asked. Please try again.</speak>"
      }
    },
    "shouldEndSession": false
  }
}
Function logs:
[WARNING]	file_cache is unavailable when using oauth2client >= 4.0.0 or google-auth
Traceback (most recent call last):
  File "/opt/python/googleapiclient/discovery_cache/file_cache.py", line 33, in <module>
    from oauth2client.contrib.locked_file import LockedFile
ModuleNotFoundError: No module named 'oauth2client'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/opt/python/googleapiclient/discovery_cache/file_cache.py", line 37, in <module>
    from oauth2client.locked_file import LockedFile
ModuleNotFoundError: No module named 'oauth2client'

During handling of the above exception, another exception occurred:


Traceback (most recent call last):
  File "/opt/python/googleapiclient/discovery_cache/__init__.py", line 44, in autodetect
    from . import file_cache
  File "/opt/python/googleapiclient/discovery_cache/file_cache.py", line 40, in <module>
    raise ImportError(
ImportError: file_cache is unavailable when using oauth2client >= 4.0.0 or google-auth[WARNING]	2020-08-11T15:14:26.911Z	d2b79235-c1ca-4b96-9782-a17058ea5e0e	Compute Engine Metadata server unavailable onattempt 1 of 3. Reason: [Errno 111] Connection refused
[WARNING]	2020-08-11T15:14:27.935Z	d2b79235-c1ca-4b96-9782-a17058ea5e0e	Compute Engine Metadata server unavailable onattempt 2 of 3. Reason: [Errno 111] Connection refused
[WARNING]	2020-08-11T15:14:28.959Z	d2b79235-c1ca-4b96-9782-a17058ea5e0e	Compute Engine Metadata server unavailable onattempt 3 of 3. Reason: [Errno 111] Connection refused
[WARNING]	2020-08-11T15:14:28.959Z	d2b79235-c1ca-4b96-9782-a17058ea5e0e	Authentication failed using Compute Engine authentication due to unavailable metadata server.
[ERROR]	2020-08-11T15:14:28.959Z	d2b79235-c1ca-4b96-9782-a17058ea5e0e	Could not automatically determine credentials. Please set GOOGLE_APPLICATION_CREDENTIALS or explicitly create credentials and re-run the application. For more information, please see https://cloud.google.com/docs/authentication/getting-started
Traceback (most recent call last):
  File "/opt/python/ask_sdk_runtime/dispatch.py", line 118, in dispatch
    output = self.__dispatch_request(handler_input)  # type: Union[Output, None]
  File "/opt/python/ask_sdk_runtime/dispatch.py", line 182, in __dispatch_request
    output = supported_handler_adapter.execute(
  File "/opt/python/ask_sdk_runtime/dispatch_components/request_components.py", line 437, in execute
    return handler.handle(handler_input)
  File "/var/task/lambda_function.py", line 104, in handle
    service = build('calendar', 'v3', credentials=accessToken)
  File "/opt/python/googleapiclient/_helpers.py", line 134, in positional_wrapper
    return wrapped(*args, **kwargs)
  File "/opt/python/googleapiclient/discovery.py", line 252, in build
    return build_from_document(
  File "/opt/python/googleapiclient/_helpers.py", line 134, in positional_wrapper
    return wrapped(*args, **kwargs)
  File "/opt/python/googleapiclient/discovery.py", line 431, in build_from_document
    credentials = _auth.default_credentials()
  File "/opt/python/googleapiclient/_auth.py", line 44, in default_credentials
    credentials, _ = google.auth.default()
  File "/opt/python/google/auth/_default.py", line 354, in default
    raise exceptions.DefaultCredentialsError(_HELP_MESSAGE)
google.auth.exceptions.DefaultCredentialsError: Could not automatically determine credentials. Please set GOOGLE_APPLICATION_CREDENTIALS or explicitly create credentials and re-run the application. 

I realized that someone else literally fell in the same problem SO. I believe in plain English, the error is saying that it can't store the access token as lambda is stateless? and numerous errors about libs. if so, could just storing it in a persistent variable do the job?
@timothyaaron Repo

Hey @danieldhz , thanks for the update. Glad to know you got pass that testing issue.

could just storing it in a persistent variable do the job?

Yeah, you can use the persistence layer to store the access token per user id/device id. The ask-sdk-dynamodb-persistence-adapter module provides this integration. You can check the high-low game sample on how to use it. Let us know if this helps.

@nikhilym Thank you so much for the confirmation! I will do it now and let you guys know :)

Is anyone else amused by their "helper functions"?
ask_sdk_core.utils.request_util.get_account_linking_access_token(handler_input)
handler_input.request_envelope.context.system.user.access_token

It literally just returns that line. You should already know what's in the request object. It's shorter and more explicit to just get what you need from where it actually exists in most cases vs memorizing longer "helper functions" that have to be imported.

Hey @timothyaaron , thanks for the feedback and sorry for the trouble. We understand the helper functions names are a bit long. Would love to hear thoughts from the community on how we can do better. I know this issue is not related to that. So, to keep this issue clean and for better organization, please go ahead with creating a new issue on the SDK repo so that we can get these helper functions to a better place.

@nikhilym My sb is occupied by CustomSkillBuilder(api_client=DefaultApiClient()) Shall I reassign it to StandardSkillBuilder(table_name="High-Low-Game", auto_create_table=True)?
As well as, I believe in the repo you mentioned a dynamodb hasn't been manually created from AWS but automatically created on the console itself?
Since I've manually created it, is doing:

table = dynamodb.Table('MyTableName')
AccessToken = handler_input.request_envelope.context.system.user.access_token
        response = table.update_item(
            Item={
                    'userId': userId,
                    'AccessToken': AccessToken 
                }
            )
 

right? even though as I am not sure how to access the userID :D
Unfortunately, I am quite unfamiliar with the whole flask structure of developing an alexa skill from the repo your linked

Actually, the ones I was referring to don't really need "a better place", in my opinion; they could just be dropped. If there's no additional logic (unlike, say, get_slot_value)—literally just a one-liner—then it's not really a "helper", it's just an alternative access point (which just adds confusion… again, in my opinion).

I, too, don't want to hijack this issue. Happy to let it go with that.

@danieldhz, looks like @nikhilym's got you taken care of. Good luck.

@danieldhz , reassigning your skill builder should work. You probably should use StandardSkillBuilder(table_name="MyTableName", auto_create_table=False), since that is your table name and since you mentioned you already created the table manually in AWS.

As for using the persistence attributes in HandlerInput through your skill, the default is to use user_id when adding rows to your table. So don't worry about the partition key unless you want it changed. Here is the interface information in case you want the partition key changed to a different attribute.

If all you need is to save and retrieve the access token through persistence, you can do

# For storing the access token in persistence
persistence_attr = handler_input.attributes_manager.persistent_attributes
persistence_attr['access_token'] = <...whatever value you want to be stored...>
handler_input.attributes_manager.save_persistent_attributes()

# For retrieving the access token
persistence_attr = handler_input.attributes_manager.persistent_attributes
access_token = persistence_attr['access_token']

@timothyaaron , yeah it is a bit confusing to have a helper function which basically is a one-liner. The idea was to have a set of commonly used retrieval utilities as helpers, so that skill devs have a go-to module to use them. It also helps us add validations in future, if needed. Btw, thanks for the feedback :-) and jumping in to help other skill developers. Please stay in the thread and respond when you can :-). Always appreciated !!

Hi @nikhilym Thanks for your comprehensive response.
I am not sure if you had the chance to check it out but I'd just like to inform you that I opened an issue regarding your recommendation as from ask_sdk.standard import StandardSkillBuilder gave me an error.

Hey @danieldhz , as mentioned in the error , you would need ask-sdk library to use the StandardSkillBuilder. You probably have ask-sdk-core which gives you the basic skill builder functionality (no persistence layer, no api client). Please change your requirements.txt to use ask-sdk, install that library and try again.

Hi @nikhilym Hope you are doing well.
Thank you so much for your support. I don't know why I thought ask-sdk was a basic version of ask-sdk-core, especially when I saw no numbers beside it. I am sorry for my false assumption. Nevertheless, I changed it and now I am facing a weird issue

Hi @danieldhz, based on the conversation I see your issue on 'NoneType' object has been resolved and you have reopened this issue which is already a duplicate from here.

I will close this issue and follow up on the other one in order to keep issue tracker clean and not mix multiples issues.