Please refer the code snippet below:
import awsgi
import json
from flask import (
Flask,
jsonify,
request
)
app = Flask(__name__)
#app.route('/')
def index():
return jsonify(status=200, message='OK')
#app.route('/tester')
def tst():
rule = request.url_rule
if 'tester' in rule.rule:
return {'status':200, 'message':'test'}
def lambda_handler(event, context):
test = (awsgi.response(app, event, context))
for key, value in test.items():
if key == 'message':
call = value
return {
'body': json.dumps(test)
}
Now in call variable we have value 'test'.
This 'test' is also the name of a method in another lambda that I want to call.
can someone please help me with this
Thanking You
Each AWS Lambda function has one entry point via the function defined as the Handler.
When the AWS Lambda function is invoked, the Handler function is called. It is not possible to 'call' another function when invoking the Lambda function.
However, you could add some logic to the Handler function that examines the incoming event and checks for a test situation. The Handler function could then call the test() function. For example, add an entry to the event that says "Test": "true", then have the Handler function check for this entry and, if present, call test().
I think you you simply would like to call another lambda.
If thats what you are looking for, here is how you can do it.
import json
import boto3
from flask import (
Flask,
jsonify,
request
)
import awsgi
client = boto3.client('lambda')
def lambda_handler(event, context):
test = 'other-function-name'
response = client.invoke(
FunctionName=test,
InvocationType='RequestResponse',
Payload=json.dumps({})
)
return {
...
'body': json.dumps(response)
....
}
If you don't like to wait for the other lambda to finish, you can use InvocationType='RequestResponse'
hope this helps.
Related
(I did find the following question on SO, but it didn't help me: Is it possible to have an api call another api, having them both in same application?)
I am making an app using Fastapi with the following folder structure
main.py is the entry point to the app
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.v1 import lines, upload
from app.core.config import settings
app = FastAPI(
title=settings.PROJECT_NAME,
version=0.1,
openapi_url=f'{settings.API_V1_STR}/openapi.json',
root_path=settings.ROOT_PATH
)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.BACKEND_CORS_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(upload.router, prefix=settings.API_V1_STR)
app.include_router(lines.router, prefix=settings.API_V1_STR)
In the lines.py, I have 2 GET endpoints:
/one-random-line --> returns a random line from a .txt file
/one-random-line-backwards --> should return the output of the /one-random-line
Since the output of the second GET endpoint should be the reversed string of the output of the first GET endpoint, I tried doing the following steps mentioned here
The codes:
import random
from fastapi import APIRouter, Request
from starlette.responses import RedirectResponse
router = APIRouter(
prefix="/get-info",
tags=["Get Information"],
responses={
200: {'description': 'Success'},
400: {'description': 'Bad Request'},
403: {'description': 'Forbidden'},
500: {'description': 'Internal Server Error'}
}
)
#router.get('/one-random-line')
def get_one_random_line(request: Request):
lines = open('netflix_list.txt').read().splitlines()
if request.headers.get('accept') in ['application/json', 'application/xml']:
random_line = random.choice(lines)
else:
random_line = 'This is an example'
return {'line': random_line}
#router.get('/one-random-line-backwards')
def get_one_random_line_backwards():
url = router.url_path_for('get_one_random_line')
response = RedirectResponse(url=url)
return {'message': response[::-1]}
When I do this, I get the following error:
TypeError: 'RedirectResponse' object is not subscriptable
When I change the return of the second GET endpoint to return {'message': response}, I get the following output
What is the mistake I am doing?
Example:
If the output of /one-random-line endpoint is 'Maverick', then the output of /one-random-line-backwards should be 'kcirevam'
You can just call any endpoint from your code directly as a function call, you don't have to deal with RedirectResponse() or anything. Below is an example of how this would look like and will run as-is:
from fastapi import FastAPI, Request
app = FastAPI()
#app.get("/one-random-line")
async def get_one_random_line(request: Request):
# implement your own logic here, this will only return a static line
return {"line": "This is an example"}
#app.get("/one-random-line-backwards")
async def get_one_random_line_backwards(request: Request):
# You don't have to do fancy http stuff, just call your endpoint:
one_line = await get_one_random_line(request)
return {"line": one_line["line"][::-1]}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Using curl we get the following result:
% curl localhost:8000/one-random-line
{"line":"This is an example"}%
% curl localhost:8000/one-random-line-backwards
{"line":"elpmaxe na si sihT"}%
Refactor your code to have the common part as a function you call - you'd usually have this in a module external to your controller.
# this function could live as LineService.get_random_line for example
# its responsibility is to fetch a random line from a file
def get_random_line(path="netflix_list.txt"):
lines = open(path).read().splitlines()
return random.choice(lines)
# this function encodes the rule that "if the accepted response is json or xml
# we do the random value, otherwise we return a default value"
def get_random_or_default_line_for_accept_value(accept, path="netflix_list.txt", default_value="This is an example"):
if accept not in ("application/json", "application/xml"):
return default_value
return get_random_line(path=path)
#router.get('/one-random-line')
def get_one_random_line(request: Request):
return {
"line": get_random_or_default_line_for_accept_value(
accept=request.headers.get('accept'),
),
}
#router.get('/one-random-line-backwards')
def get_one_random_line_backwards(request: Request):
return {
"line": get_random_or_default_line_for_accept_value(
accept=request.headers.get('accept'),
)[::-1],
}
Currently, in our system, we are calling the endpoints even in the same flask application by a HTTP request. All the requests is called through a make_request wrapper method as shown below:
def make_request(url, body, http_type="get"):
http_fn = getattr(requests, http_type)
response = http_fn(url, headers=headers, json=body)
return response.status_code, response
Hence I'm trying to convert all local requests within the same flask application to a direct method call so that any endpoint within the same flask application is called this way:
def make_request(url, body, http_type="get"):
# Figure out If its local request call the function of the endpoint and construct the response
# If not make an http request
return response.status_code, response
EDIT: Tried searching in the url_map to find the method associated with the endpoint but the function returned in not in a callabale state. Any points on how we can call the method from here?
for rule in current_app.url_map.iter_rules():
if my_url in rule.rule:
endpoint = rule.endpoint
for key, view in current_app.view_functions.items():
if key == endpoint:
# Found the view function, need to know how to call
# the right method( GET, POST etc)
view contains the following:
{
'view_class': <class 'endpoints.attribute_endpoints.AttributeEndpoint'>,
'methods': {'GET', 'PUT', 'POST'}, 'provide_automatic_options': None, '__wrapped__': <function View.as_view.<locals>.view at 0x10c9190d0>}
If I understand correctly, what you're trying to achieve is calling a flask endpoint internally without going over http. Look at the solution below and let me know if it is does what you want.
Old Code:
#app.route('/someRoute', methods=['GET'])
def some_route_function():
json_object = request.get_json()
my_number = json_object['myNumber']
my_number = my_number**2
return jsonify(my_number=my_number)
New Code:
def square_number_func(number):
return number**2
#app.route('/someRoute', methods=['GET'])
def some_route_function():
json_object = request.get_json()
my_number = json_object['myNumber']
my_number = square_number_func(my_number)
return jsonify(my_number=my_number)
def my_non_flask_function():
my_number = 17
my_number = square_number_func(my_number)
This way you get the functionality you need without having to rely on Flask's request object, nor having to call flask via http.
Edit: If you need to figure out if it's an internal flask function then you compare it against a list of functions in your local global scope or in your flask app, as it does store your routes. You can even store the function parameters so you know what to call. Finally, you can map each endpoint to another function if you want inside a dictionary or something, such as {some_route_function: square_number_func} so that you can tell which function to substitute for the http call.
Here's my backend structure:
Here's my app.py:
from flask import Flask
app = Flask(__name__)
#app.route('/', methods=['GET'])
def test_backend():
return "This is the test function for backend without lambda"
if __name__ == '__main__':
app.run(debug=True)
and lambda_handler in event_lambda.py:
def lambda_handler(event=None, context=None):
""" This lambda triggers other supporting functions """
return "This lambda handler triggers other functions "
I've tried to invoke lambda function through the following event in zappa_settings.json
"events": [{
"function": "backend.event_lambda.lambda_handler",
"expression": "cron(0 9 1 * ? *)"
}],
But it only returns "This is the test function for backend without lambda" from the app.py. The lambda function is invoked only when I invoke it manually using the command:
zappa invoke backend.event_lambda.lambda_handler
How can I set zappa to invoke the lambda function directly?
Im not sure if this is exactly what you are looking for, but this response https://stackoverflow.com/a/62119981 was a godsend for me while I was trying to invoke a function that was within zipped API deployed via Zappa on AWS Lambda.
Relevant fragment in source code:
https://github.com/zappa/Zappa/blob/fff5ed8ad2ee071896a94133909adf220a8e47d9/zappa/handler.py#L380
TL;DR: use command keyword in payload of what is send to lambda_handler to invoke any function in your API.
so, if you would like to invoke your lambda_handler function which is part of an zipped API deployed on Lambda, you can do it via boto3:
from json import dumps
import boto3
lambda_client = boto3.Session().client(
service_name='lambda',
region_name='eu-central-1' # or other region
)
response = lambda_client.invoke(
FunctionName=<lambda_arn>,
Payload=dumps(
{
"command": "xxx_backend.event_lambda.lambda_handler", # xxx is the blacked out fragment of the name
}
)
)
And in response, apart from some Metadata, you should receive desired ("This lambda handler triggers other functions ") output.
It's also possible to pass some data into handler, BUT im not sure if there is any recommended keyword, so you can use any (beside those which are reserved!). Im using 'data'.
So, we will change your lambda_handler function a little bit:
def lambda_handler(event=None, context=None):
""" This lambda triggers other supporting functions """
return f"This lambda handler received this payload: {event["data"]}"
The invokation has to change too:
from json import dumps
import boto3
lambda_client = boto3.Session().client(
service_name='lambda',
region_name='eu-central-1' # or other region
)
response = lambda_client.invoke(
FunctionName=<lambda_arn>,
Payload=dumps(
{
"command": "xxx_backend.event_lambda.lambda_handler", # xxx is the blacked out fragment of the name
"data": "Hello from Lambda!"
}
)
)
And the response from the invokation should look like this now:
This lambda handler received this payload: Hello from Lambda!"
Hope this helps.
Try:
zappa schedule <environment>
I'm working on a simple REST service with flask, the method deleteTweets() can't retrieve the URL parameter i'm sending, so tried to print to see what is sending but i get this error line.
File "C:\Path\HomeController.py", line 26, in deleteTwee
ts
id = request.args.get['id']
AttributeError: 'function' object has no attribute 'args'
127.0.0.1 - - [17/Jan/2018 12:00:05] "DELETE /api/deleteTweet?id=ABC HTTP/1.1" 500
This the code:
import json
import Service
from flask import Flask, render_template,jsonify,request
app = Flask(__name__)
apiUrl='/api'
#app.route('/')
def hello_world():
return render_template("/home.html", code=400)
#app.route(apiUrl+'/getData', methods=['GET'])
def test_request():
list = [15,21,8]
return jsonify(list)
#app.route(apiUrl+'/getTweets', methods=['GET'])
def request():
return Service.getAlltwits()
#app.route(apiUrl+'/deleteTweet', methods=['DELETE'])
def deleteTweets():
id = request.args.get['id']
print(id)
return
It's very simple but not sure what i did missed.
also tried with id = request.args.get('id')
You're doing the correct thing - and you're importing request.
But you have a function named request as well, and this function overwrites the former name (which is the actual flask request):
#app.route(apiUrl+'/getTweets', methods=['GET'])
def request():
return Service.getAlltwits()
Change the name to get_tweets instead:
#app.route(apiUrl+'/getTweets', methods=['GET'])
def get_tweets():
return Service.getAlltwits()
You've defined a function called request, which has therefore hidden the global request variable. Rename your route function.
I'm trying to create simple Lambda function using Python 3.6.
The function should get a userId (my primary key in DynamoDB) in the request query string params and returns 200 if item exist in DB, here is my lambda function
import boto3
import os
from boto3.dynamodb.conditions import Key, Attr
def lambda_handler(event, context):
userId = event["userId"]
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['Customers'])
items = table.query(
KeyConditionExpression=Key('userId').eq(userId)
)
return items["Items"]
When i am doing tests in Lambda interface it works and return the correct user however, when trying from Postman or using API Gateway it returns the following error
{
"errorMessage": "'userId'",
"errorType": "KeyError",
"stackTrace": [
[
"/var/task/index.py",
7,
"lambda_handler",
"userId = event["userId"]"
]
]
}
What am i missing here ?
Struggling to understand "event" , documentation states its a python
dictionary but how can i print the result of it and actually debug the lambda
when called from Postman or API Gateway?
You are using event["userId"], this means that sending the request payload for example
GET API : api/users/
Request Body payload:
{
"userId":"1234"
}
then above code works, Suppose you want to send userId as path parameter
GET API :api/user/{userId}
then you can access in lambda function
userId = (event['pathparameters']['userId'])
better add the print statement
print(event) and check the logs in cloudwatch logs
This solved it for me on post requests
import json
def lambda_handler(event, context):
data = json.loads(event["body"])
email = data['email']
in case you are using the serverless framework you can also add the following code under your http event. but i dont think it is that necessary.
request:
parameters:
application/json: '{"email":"$input.params(''email'')"}'
Make sure you hadn't selected "Lambda Proxy" while creating the HTTP method. Proxy will not convert/modify the request and hence "event" will be null
In my case my Python Lambda required a key called exclude. To resolve the issue of getting this response when calling via API Gateway, I needed to update the integration request with a mapping template: