HttpTrigger Execute one after another using python - python

I have created one HttpTrigger Azure functions using python. But I want to create another one HttpTrigger Azure Functions using python in the same project. In this application I want to execute first HttpTrigger Azure functions after that second HttpTrigger Azure functions execute. How can I implement that?
Because in Python Azure Functions there is no Durable Functions. That's why I am not able to understood how can I execute one after another azure function executes.

Assumed there are two HttpTrigger functions HttpTriggerA and HttpTriggerB, a direct solution in my mind is to request the public url of HttpTriggerA from HttpTriggerB function via a HTTP client request using requests, as the code below in my first version of HttpTriggerB code.
import logging
import azure.functions as func
import requests
from urllib.parse import urlparse
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
name = req.params.get('name') or 'Peter Pan'
codeA = req.params.get('codeA') or ''
o = urlparse(req.url)
if not name:
try:
req_body = req.get_json()
except ValueError:
pass
else:
name = req_body.get('name')
if name:
print("B be invoked.")
resp = requests.get(f"{o.scheme}://{o.netloc}/api/HttpTriggerA?name={name}&code={codeA}")
return func.HttpResponse(f"Hello {name}! from B {resp.status_code == 200 and resp.text or ''}")
else:
return func.HttpResponse(
"Please pass a name on the query string or in the request body",
status_code=400
)
However, it will not work after I test the code above on local or on Azure. It will hang at the code line resp = requests.get(f"{o.scheme}://{o.netloc}/api/HttpTriggerA?name={name}&code={codeA}"), when I access the url http(s)://<the host of local or Azure>:<7071 or 80>/api/HttpTriggerB?name=Peter%20Pan&code=<code for HttpTriggerB>&codeA=<code for HttpTriggerA>. The reason for the hang issue seems to be caused by functions running in singleton or in single thread.
So I switched to the other solution to use Ajax request from the html content of HttpTriggerB. It works as I wish as the figure below.
Here is my code for HttpTriggerB function, the HttpTriggerA function is simply generated by func new.
import logging
import azure.functions as func
#import requests
from urllib.parse import urlparse
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
name = req.params.get('name') or 'Peter Pan'
codeA = req.params.get('codeA') or ''
o = urlparse(req.url)
if not name:
try:
req_body = req.get_json()
except ValueError:
pass
else:
name = req_body.get('name')
if name:
print("B be invoked.")
#resp = requests.get(f"{o.scheme}://{o.netloc}/api/httptriggera?name={name}&code={req.params.get('codeA')}")
html = """
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
Hello """+name+"""! from B<br/>
<span id="A"></span>
<script>
$.when(
$.get('"""+o.scheme+'://'+o.netloc+'/api/HttpTriggerA?name='+name+'&code='+codeA+"""'),
$.ready
).done(function( data ) {
$( "#A" ).html( data[0] );
});
</script>
"""
return func.HttpResponse(html, mimetype="text/html")
else:
return func.HttpResponse(
"Please pass a name on the query string or in the request body",
status_code=400
)
Hope it helps.

Related

Azure Function App using python: How to access user groups for authorization

I am very new to Azure Function Apps and OAuth so please bear with me.
My Setup
I have an Azure Function App with a simple python-function doing nothing else but printing out the request headers:
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:
aadIdToken = req.headers.get('X-MS-TOKEN-AAD-ID-TOKEN')
aadAccessToken = req.headers.get('X-MS-TOKEN-AAD-ACCESS-TOKEN')
principalID = req.headers.get('X-MS-CLIENT-PRINCIPAL-ID')
principalName = req.headers.get('X-MS-CLIENT-PRINCIPAL-NAME')
idProviderId = req.headers.get('X-MS-CLIENT-PRINCIPAL-IDP')
aadRefreshToken = req.headers.get('X-MS-TOKEN-AAD-REFRESH-TOKEN')
clientPrincipal = req.headers.get('X-MS-CLIENT-PRINCIPAL')
result = "\n"
myDict = sorted(dict(req.headers))
for key in myDict:
result += f"{key} = {dict(req.headers)[key]}\n"
return func.HttpResponse(
f"Hello, {name}. How are you ? Doing well ?"\
f"\n\nHere is some data concerning your Client principal:"\
f"\nThis is your X-MS-CLIENT-PRINCIPAL-ID: {principalID}"\
f"\nThis is your X-MS-CLIENT-PRINCIPAL-NAME: {principalName}"\
f"\nThis is your X-MS-CLIENT-PRINCIPAL-IDP: {idProviderId}"\
f"\nThis is your X-MS-CLIENT-PRINCIPAL: {clientPrincipal}"\
f"\n\nHere is some data concerning your AAD-token:"\
f"\nThis is your X-MS-TOKEN-AAD-ID-TOKEN: {aadIdToken}"\
f"\nThis is your X-MS-TOKEN-AAD-ACCESS-TOKEN: {aadAccessToken}"\
f"\nThis is your X-MS-TOKEN-AAD-REFRESH-TOKEN: {aadRefreshToken}"\
f"\n\n\nresult: {result}"\
)
else:
return func.HttpResponse(
"This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.",
status_code=200
)
I followed this guide to let the user authenticate via EasyAuth before calling the function.
This seems to work fine. When accessing the function via browser I am redirected to sign-in. After successful sign-in I am then redirected again and the HTTP response is printed out in the browser. As I am able to access X-MS-CLIENT-PRINCIPAL-ID and X-MS-CLIENT-PRINCIPAL-NAME I suppose the authentication was successful. However when printing out the whole request header I did not find a X-MS-TOKEN-AAD-REFRESH-TOKEN, X-MS-TOKEN-AAD-ACCESS-TOKEN or X-MS-TOKEN-AAD-ID-TOKEN.
This is the output (output too large; below the output shown in the screenshot I can see the header content):
First half of my output
My question
What I am trying to do now is to access the groups assigned to the logged-in user via the python code of the function to further authorize his request (e.g. "user can only execute the function when group xyz is assigned, else he will be prompted 'not allowed'").
To achieve this I added the "groups"-claim to the Token Configuration of my App Registration.
From what I understand accessing the user groups via a function coded in .NET is easily possible by using the ClaimsPrinciple object (source).
How would I be able to access the user assigned groups via python code?
Is that possible?
Am I understanding something completely wrong?
Followup:
One thing that I do not understand by now, is that I can see an id_token in the callback-http-request of the browser-debuggger when accessing the function via browser for the first time (to trigger sign in):
Browser debugger: id_token in callback-request
When I decrypted that token using jwt.io I was able to see some IDs of assigned user groups which seems to be exactly what I want to access via the python code.
Re-loading the page (I suppose the request then uses the already authenticated browser session) makes the callback disappear.
The header X-MS-CLIENT-PRINCIPAL contains the same claims as the id_token. So if we want to get the group claim, we can base64 decode the header.
For example
My code
import logging
import azure.functions as func
import base64
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:
aadAccessToken = req.headers.get('X-MS-TOKEN-AAD-ACCESS-TOKEN')
principalID = req.headers.get('X-MS-CLIENT-PRINCIPAL-ID')
principalName = req.headers.get('X-MS-CLIENT-PRINCIPAL-NAME')
idProviderId = req.headers.get('X-MS-CLIENT-PRINCIPAL-IDP')
aadRefreshToken = req.headers.get('X-MS-TOKEN-AAD-REFRESH-TOKEN')
clientPrincipal = req.headers.get('X-MS-CLIENT-PRINCIPAL')
clientPrincipal= base64.b64decode(clientPrincipal)
result = "\n"
myDict = sorted(dict(req.headers))
for key in myDict:
result += f"{key} = {dict(req.headers)[key]}\n"
return func.HttpResponse(
f"Hello, {name}. How are you ? Doing well ?"\
f"\n\nHere is some data concerning your Client principal:"\
f"\nThis is your X-MS-CLIENT-PRINCIPAL-ID: {principalID}"\
f"\nThis is your X-MS-CLIENT-PRINCIPAL-NAME: {principalName}"\
f"\nThis is your X-MS-CLIENT-PRINCIPAL-IDP: {idProviderId}"\
f"\nThis is your X-MS-CLIENT-PRINCIPAL: {clientPrincipal}"\
f"\n\nHere is some data concerning your AAD-token:"\
f"\nThis is your X-MS-TOKEN-AAD-ID-TOKEN: {aadIdToken}"\
f"\nThis is your X-MS-TOKEN-AAD-ACCESS-TOKEN: {aadAccessToken}"\
f"\nThis is your X-MS-TOKEN-AAD-REFRESH-TOKEN: {aadRefreshToken}"\
f"\n\n\nresult: {result}"\
)
else:
return func.HttpResponse(
"This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.",
status_code=200
)

Snowflake External Functions using Azure Functions on Python not working

I want to create an external function that can be used to upsert rows into MongoDB. I've created the function, tested it locally using Postman and after publishing. I've followed the documentation from https://docs.snowflake.com/en/sql-reference/external-functions-creating-azure-ui.html and at first, I used the javascript function they proposed to test and worked. However, when I run it it python I get an error. This is the code.
import logging
import azure.functions as func
import pymongo
import json
import os
from datetime import datetime
cluster = pymongo.MongoClient(os.environ['MongoDBConnString'])
db = cluster[f"{os.environ['MongoDB']}"]
collection = db[f"{os.environ['MongoDBCollection']}"]
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}. This HTTP triggered function executed successfully.")
else:
collection.update_one(
filter={
'_id':req_body['_id']
},
update={
'$set': {'segment_ids': req_body['segment_ids']}
},
upsert=True)
return func.HttpResponse(
json.dumps({"status_code": 200,
"status_message": "Upsert Success",
"Timestamp": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S"),
"_id": req_body['_id']}),
status_code=200,
mimetype="text/plain"
)
The error states that req_body is referenced before being defined, failing at line '_id':req_body['_id']. In Snowflake I've created an external function called mongoUpsert(body variant) and I am parsing a simple query to test.
select mongoUpsert(object_construct('_id', 'someuuid', 'segment_ids;, array_construct(1,2,3,4)))
From what I can tell, the function is not receiving the body I'm parsing in Snowflake for some reason. I don't know what I am doing wrong. Can anyone help me? Can anyone also explain how Snowflake is sending the parameters (as body, params, headers) and is there a way to specify if I want to parse a body or params?
External functions send and receive data in a particular format. All the parameters are sent in the request body.
https://docs.snowflake.com/en/sql-reference/external-functions-data-format.html
You can checkout snowflake-labs
for external functions samples.
There is one specifically for Azure Python functions that calls the Translator API.
I've started from scratch and stripped the layers one by one in Snowflake. So the Snowflake parameter is parsed to the body of the function but wrapped in an array which is then wrapped in another object called 'data'. Furthermore, it expects the same schema as a response back. So here's below the template to use for Azure Functions when using Python.
import logging
import azure.functions as func
import json
def main(req: func.HttpRequest) -> func.HttpResponse:
# Get body response from Snowflake
req_body = req.get_json()['data'][0][1]
###### Do Something
# Return Response
message = {"Task": "Completed"}
return func.HttpResponse(
json.dumps({'data': [[0, message]]}),
status_code=200)
As an example, I've used a simple JSON object:
{
"_id": "someuuid"
}
And created an external function in Snowflake called testfunc(body variant) and called it using select testfunc(object_construct('_id', 'someuuid')).
If you would log the response (using logging.info(req.get_json())) it would print the following
{
"data":
[
[
0,
{
"_id": "someuuid"
}
]
]
}
So to get the clean input I fed in snowflake I have the line
req_body = req.get_json()['data'][0][1]
However, I kept getting errors on the response until I tried just echoing the input and noticed it returned it without the wrapping. The returned body needs to be a string (hence why using json.dumps()) but it also needs the wrapping. So to print it out, first define a message you want (it may be a calculation of the input or an acknowledgement), then wrap the message in {'data': [[0, message]]} and finally compile it as a string (json.dumps())

azure functions return http response from a custom util function

Suppose I have an API for signup. I want to get data from the request and validate it in a Util function that I wrote and placed in another directory (see signup API and my Util directory in the picture). If validation goes fail, I want to return the HTTP response directly from that Util function. How can I achieve this?
In the init.py
from utils.common_utils import validate
def main(req: func.HttpRequest) -> func.HttpResponse:
req_body = validate(req)
# other stuff
return func.HttpResponse("ok", status_code=200)
and in my util function (validate function)
def validate(input):
if ...:
return func.HttpResponse("check your input", status_code=406)
As far as I know, I don't think it can return HttpResponse directly from that Util function and exit the main function. In your code, you can just return HttpResponse to req_body and then execute the following code of main function.
But I think if you write the code like below, it can also meet your requirement. It's the same thing.
def main(req: func.HttpRequest) -> func.HttpResponse:
req_body = validate(req)
if req_body.status_code == 406:
return req_body
return func.HttpResponse("ok", status_code=200)

Azure Functions with Multiple Python Files

I am unable to import my other python file in the init.py file. I have tried importing that according to the same way as mentioned in the documentation.
https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-python#import-behavior
This is my init.py file:
import logging
import azure.functions as func
from . import test
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
print("test")
num = sum_num(2, 3)
print(num)
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}. This HTTP triggered function executed successfully.")
else:
return func.HttpResponse(
"This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.",
status_code=200
)
this is my test.py file with very basic sum function:
def sum_num(val1, val2):
print("inside function")
return val1 + val2
Please help me out how to import this sum_num function in my init.py file.
the folder structure is also fine as you can see:
Error i am getting in the logs
You have already imported the module using
from . import test
so in order to access the function from the module you should use
num = test.sum_num(2, 3)
Alternatively you can import only the function from the module.
from .test import sum_num

How to return HTML file in HTTP Response (Azure-Functions)

I'm learning to use azure-functions and I want to know how can I return an HTML file on this piece of code. (the initial python code for azure-functions)
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
)
What I want is something like:
return func.HttpResponse("\index.html")
How can I do this?
Assumed that you are following the offical Quickstart tutorial Create an HTTP triggered function in Azure to learn Azure Function for Python, then you created a function named static-file to handle these static files in the path static-file or other path you want of MyFunctionProj like index.html, logo.jpg and so on.
Here is my sample code to do that as below.
import logging
import azure.functions as func
import mimetypes
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}!")
path = 'static-file' # or other paths under `MyFunctionProj`
filename = f"{path}/{name}"
with open(filename, 'rb') as f:
mimetype = mimetypes.guess_type(filename)
return func.HttpResponse(f.read(), mimetype=mimetype[0])
else:
return func.HttpResponse(
"Please pass a name on the query string or in the request body",
status_code=400
)
The result in browser as the figure below.
The file structure of my static-file api as below.
The content of the index.html file is as below.
<html>
<head></head>
<body>
<h3>Hello, world!</h3>
<img src="http://localhost:7071/api/static-file?name=logo.jpg"></img>
</body>
</html>
Note: for running on local, the index.html file will works fine to display logo.jpg. If deploy to Azure, you need to add the query parameter code to the end of property src of tag img, such as <img src="http://<your function name>.azurewebsites.net/api/static-file?name=logo.jpg&code=<your code for /api/static-file>"></img>.
Hope it helps.
I did simple, do not mind the content (uploading a file), it is not working that way :)
if command:
return func.HttpResponse(status_code=200,headers={'content-type':'text/html'},
body=
"""<!DOCTYPE html>
<html>
<body>
<form enctype = "multipart/form-data" action = "returnNameTrigger?save_file.py" method = "post">
<p>File: <input type = "file" name = "filename" /></p>
<p><input type = "submit" value = "Upload" /></p>
</form>
</body>
</html>
""")
The accepted answer no longer works. Now you need to use the context to find the correct folder. Something like the code below should work.
import logging
import azure.functions as func import mimetypes
def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
logging.info('processed request for home funciton')
filename = f"{context.function_directory}/static/index.html"
with open(filename, 'rb') as f:
mimetype = mimetypes.guess_type(filename)
return func.HttpResponse(f.read(), mimetype=mimetype[0])

Categories

Resources