How to invoke another lambda async and pass context to it? - python

I'm trying to get working two basic lambdas using Python2.7 runtime for SQS message processing. One lambda reads from SQS invokes and passes data to another lambda via context. I'm able to invoke the other lambda but the user context is empty in it. This is my code of SQS reader lambda:
import boto3
import base64
import json
import logging
messageDict = {'queue_url': 'queue_url',
'receipt_handle': 'receipt_handle',
'body': 'messageBody'}
ctx = {
'custom': messageDict,
'client': 'SQS_READER_LAMBDA',
'env': {'test': 'test'},
}
payload = json.dumps(ctx)
payloadBase64 = base64.b64encode(payload)
client = boto3.client('lambda')
client.invoke(
FunctionName='LambdaWorker',
InvocationType='Event',
LogType='None',
ClientContext=payloadBase64,
Payload=payload
)
And this is how I'm trying to inspect and print the contents of context variable inside invoked lambda, so I could check logs in CloudWatch:
memberList = inspect.getmembers(context)
for a in memberList:
logging.error(a)
The problem is nothing works and CloudWatch shows user context is empty:
('client_context', None)
I've tried example1, example2, example3, example4
Any ideas?

I gave up trying to pass the data through the context. However, I was able to pass the data through the Payload param:
client.invoke(
FunctionName='LambdaWorker',
InvocationType='Event',
LogType='None',
Payload=json.dumps(payload)
)
And then to read it from event parameter inside invoked lambda:
ctx = json.dumps(event)

The code in the question is very close. The only issue is the InvocationType type:
This will work with the code in your question:
client.invoke(
FunctionName='LambdaWorker',
InvocationType='RequestResponse',
LogType='None',
ClientContext=payloadBase64
)
However this changes the invocation to synchronous which may be undesirable. The reason for this behavior is not clear.

Related

boto3 lambda payload always returns NULL [python, sync invoked]

I am using Lambda to trigger a step function. It works fine in terms of triggering the step function, but I also need the lambda to return the state machine execution arn (NOT state machine arn). I need the execution arn because I am implementing the whole process as a github action workflow, so I need it to check the status (running/success/failed/aborted) of the state machine.
My code of getting the lambda return for github action, wrapped as docker-compose service:
client = boto3.client("lambda", region_name="us-west-1")
lambda_response = client.invoke(
FunctionName="my-lambda",
InvocationType="RequestResponse",
Payload=json.dumps({"detail-type": "gh-action"}),
)
payload = json.loads(lambda_response["Payload"].read()) # tried .decode() too
print("payload:", payload) # payload prints None as a whole, not {"sfn_exe_arn": None}
The relevant part of my lambda function:
try:
client = boto3.client("stepfunctions")
response = client.start_execution(
stateMachineArn=STATE_MACHINE_ARN,
name=run_name,
input=json.dumps(
{"runName": run_name, "model_name": MODEL_NAME, "queries": QUERIES}
),
)
sfn_exe_arn = response["executionArn"]
except Exception as e:
raise e
return {"sfn_exe_arn": sfn_exe_arn}
# this `sfn_exe_arn` can print out with expected value in console
# but it does not return when called in github action
When I invoke this lambda from the console, most of the time it returns as expected, which is {"sfn_exe_arn": sfn_exe_arn}, but sometime it also returns null.
When I invoke this lambda as part of github action workflow, the return is always null (the lambda_response is returned, just the payload part is always null)
Can anyone help me understand why there is this gap? apparently my lambda got the executionArn, but it just doesn't return to the client.invoke()
The entire lambda_response (it is named response in the screenshot):
You have to decode the bytestream that you get from StreamingBody.read()
add .decode() to the bytes object you get from reading the reponse payload.
payload = json.loads(lambda_response["Payload"].read().decode())
My bad, I didn't try thoroughly other posts' answers. Thanks #jarmod pointed out the solution in the comments: you need to assign StreamingBody to a variable before read it. Link: Lambda Return Payload botocore.response.StreamingBody object prints but then empty in variable

Python Azure function with http trigger not seeing json body

I am building locally a Blazor server app which calls an azure function written in python. I am developing both on my local machine using visual studio for the Blazor app and VS code for the python function. The Python is 3.8.7
The Blazor app sends data to the azure function at http://localhost:7071/api/xxxxx using PostAsJsonAsync as json data in the body. I have tested that that works using webhook.site. The JSON data is (mainly) a base64 encoded .wav file.
The call to PostAsJsonAsync seems to be seen by the python azure function and works "a bit" as if I add a parameter to the call I can read it. However the python function always reports the body as being of zero length.
What am I doing wrong?
Check if you are sending the request like below:
var modelNew = new Model() { Description = "willekeurige klant", Name = "John Doe" }; response = await client.PostAsJsonAsync("api/ModelsApi/", modelNew);
if (response.IsSuccessStatusCode) //check is response succeeded
{
// Do something
}
And, reading it like below:
import logging
import azure.functions as func
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
name = req.params.get('name')
if not name:
try:
req_body = req.get_json()
except ValueError:
pass
else:
name = req_body.get('name')
if name:
return func.HttpResponse(f"Hello {name}!")
else:
return func.HttpResponse(
"Please pass a name on the query string or in the request body",
status_code=400
)
Check if you are using this code to encode it:
private static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
Further, pls consider using files like .wav in Azure Blob Storage and pass it's url location instead of the whole object for better security.

Python Lambda giving botocore.errorfactory.InvalidLambdaResponseException when triggered on postconfirmation

I have setup AWS Lambda function that is triggered on an AWS Cognito. The trigger on a successful email confirmation. The Lambda function is in Python3.6.
I am referring to the AWS documentation for Cognito postConfirmation trigger.
https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html
"response": {}
So far I have tried returning None, {}, '{}'(empty json string) or valid dictionary like {'status':200, 'message':'the message string'} but it is giving error.
botocore.errorfactory.InvalidLambdaResponseException: An error occurred (InvalidLambdaResponseException) when calling the ConfirmSignUp operation: Unrecognizable lambda output
What should be a valid response for the post confirmation function?
here is the part of code.
from DBConnect import user
import json
def lambda_handler(event, context):
ua = event['request']['userAttributes']
print("create user ua = ", ua)
if ('name' in ua):
name = ua['name']
else:
name = "guest"
newUser = user.create(
name = name,
uid = ua['sub'],
owner = ua['sub'],
phoneNumber = ua['phone_number'],
email = ua['email']
)
print(newUser)
return '{}' # <--- I am using literals here only.
You need to return the event object:
return event
This is not obvious in the examples they provide in the documentation. You may want to check and ensure the event object does contain a response key (it should).

Python Lambda function access cognito "sub" uuid

I have a Lambda function written in python that is triggered by the API Gateway with proxy integration and a Cognito user pool authorizer. I am trying to access the "sub" UUID that Cognito gives every user, but nothing I try works. I have just about exhausted google of all related search results and nothing that I have found seems valid. They either return a null or they crash. My function (with every attempted UUID access line commented out) is below:
import json
def lambda_handler(event, context):
#UniqueUser = context.identity.cognito_identity_id
#UniqueUser = context.authorizer.claims.sub
#UniqueUser = event.requestContext.authorizer.claims.sub
#UniqueUser = event.request.userAttributes.sub
#UniqueUser = event.queryStringParameters.sub
try:
# Something that gets sub from cognito
except:
UniqueUser = "not valid code"
if not UniqueUser:
UniqueUser = "UniqueUser is null"
return {
'statusCode': 200,
'headers': {
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps('Hello from Lambda ' + UniqueUser + "!")
};
Does anyone know any possible sources of this behavior or solution to this problem?
If you need to access the UUID of a Cognito user who is calling a Lambda function through the API Gateway with proxy integration, use the code below.
UniqueUser = event['requestContext']['authorizer']['claims']['sub']

How to process JSON in Flask?

I have asked a few questions about this before, but still haven't solved my problem.
I am trying to allow Salesforce to remotely send commands to a Raspberry Pi via JSON (REST API). The Raspberry Pi controls the power of some RF Plugs via an RF Transmitter called a TellStick. This is all setup, and I can use Python to send these commands. All I need to do now is make the Pi accept JSON, then work out how to send the commands from Salesforce.
Someone kindly forked my repo on GitHub, and provided me with some code which should make it work. But unfortunately it still isn't working.
Here is the previous question: How to accept a JSON POST?
And here is the forked repo: https://github.com/bfagundez/RemotePiControl/blob/master/power.py
What do I need to do? I have sent test JSON messages n the Postman extension and in cURL but keep getting errors.
I just want to be able to send various variables, and let the script work the rest out.
I can currently post to a .py script I have with some URL variables, so /python.py?power=on&device=1&time=10&pass=whatever and it figures it out. Surely there's a simple way to send this in JSON?
Here is the power.py code:
# add flask here
from flask import Flask
app = Flask(__name__)
app.debug = True
# keep your code
import time
import cgi
from tellcore.telldus import TelldusCore
core = TelldusCore()
devices = core.devices()
# define a "power ON api endpoint"
#app.route("/API/v1.0/power-on/<deviceId>",methods=['POST'])
def powerOnDevice(deviceId):
payload = {}
#get the device by id somehow
device = devices[deviceId]
# get some extra parameters
# let's say how long to stay on
params = request.get_json()
try:
device.turn_on()
payload['success'] = True
return payload
except:
payload['success'] = False
# add an exception description here
return payload
# define a "power OFF api endpoint"
#app.route("/API/v1.0/power-off/<deviceId>",methods=['POST'])
def powerOffDevice(deviceId):
payload = {}
#get the device by id somehow
device = devices[deviceId]
try:
device.turn_off()
payload['success'] = True
return payload
except:
payload['success'] = False
# add an exception description here
return payload
app.run()
Your deviceID variable is a string, not an integer; it contains a '1' digit, but that's not yet an integer.
You can either convert it explicitly:
device = devices[int(deviceId)]
or tell Flask you wanted an integer parameter in the route:
#app.route("/API/v1.0/power-on/<int:deviceId>", methods=['POST'])
def powerOnDevice(deviceId):
where the int: part is a URL route converter.
Your views should return a response object, a string or a tuple instead of a dictionary (as you do now), see About Responses. If you wanted to return JSON, use the flask.json.jsonify() function:
# define a "power ON api endpoint"
#app.route("/API/v1.0/power-on/<int:deviceId>", methods=['POST'])
def powerOnDevice(deviceId):
device = devices[deviceId]
# get some extra parameters
# let's say how long to stay on
params = request.get_json()
try:
device.turn_on()
return jsonify(success=True)
except SomeSpecificException as exc:
return jsonify(success=False, exception=str(exc))
where I also altered the exception handler to handle a specific exception only; try to avoid Pokemon exception handling; do not try to catch them all!
To retrieve the Json Post values you must use request.json
if request.json and 'email' in request.json:
request.json['email']

Categories