Version 2 python Azure Functions are not visible after deployment with Pulumi because of imports

223 Views Asked by At

I'm using Python and Pulumi to deploy version 2 Azure Functions with Python runtime v4 and Linux OS. The functions I want to create are HTTP triggered and have some additional functionality including database calls and some extra configs. The structure of the folder I am using:

enter image description here

Initially I tried to deploy an example function (taken from here after adapting the example to version 2) everything works fine but when I add imports (from another Python module because I want my code to be clear and organized) the functions are not visible after deployment. My functions are version 2, so they are declared in the same file using decorators. After searching I saw the following options:

  • declare additional functions in additional_functions.py using blueprints as described here -not necessary an option for me because I don't need additional routes inside my app

  • I tried to pack my additional functions inside a custom python package, based on a .whl file. I added the .whl file in the folder and the dependency in requirements.txt. I did the deployment, the functions are still not visible

  • the third option is to declare the additional functions as internal functions inside the function app - not necessary a good practice, I want my code modular

I can show a limited code snippet I have used. The main file:


    import pulumi
    import pulumi_azure_native as azure
    
    resource_group = azure.resources.get_resource_group(...) # get existing resource group
    
    account = azure.storage.get_storage_account(...) # get existing storage account
    
    primary_key = pulumi.Output.all(resource_group.name, account.name).apply(
        lambda args: azure.storage.list_storage_account_keys(resource_group_name=args[0], account_name=args[1])
    ).apply(lambda accountKeys: accountKeys.keys[0].value) # get connection string to storage account
    
    
    app_container = azure.storage.BlobContainer(
        "container-name",
        account_name=account.name,
        resource_group_name=resource_group.name,
        public_access=azure.storage.PublicAccess.NONE,
    ) # create a storage container
    
    
    app_blob = azure.storage.Blob(
        "blob-name",
        account_name=account.name,
        resource_group_name=resource_group.name,
        container_name=app_container.name,
        source=pulumi.FileArchive("."), # upload current folder, including the .whl file
    ) # upload the serverless app to the storage container
    
    
    signature = (
        pulumi.Output.all(resource_group.name, account.name, app_container.name)
        .apply(
             ....
            )
        )
        .apply(lambda result: result.service_sas_token)
    ) # create a shared access signature to give the function app access to the code
    
    
    plan = azure.web.AppServicePlan(
        "plan",
        resource_group_name=resource_group.name,
        kind="Linux",
        reserved=True,
        sku=azure.web.SkuDescriptionArgs(
            name="Y1",
            tier="Dynamic",
        ),
    ) # create service plan
     
    app = azure.web.WebApp(
        "app",
        resource_group_name=resource_group.name,
        server_farm_id=plan.id,
        kind="FunctionApp",
        site_config=azure.web.SiteConfigArgs(
            linux_fx_version="PYTHON|3.11",
            app_settings=[
                azure.web.NameValuePairArgs(
                    name="FUNCTIONS_WORKER_RUNTIME",
                    value="python",
                ),
                azure.web.NameValuePairArgs(
                    name="FUNCTIONS_EXTENSION_VERSION",
                    value="~4",
                ),
                azure.web.NameValuePairArgs(
                    name="WEBSITE_RUN_FROM_PACKAGE",
                    value=pulumi.Output.all(
                        account.name, app_container.name, app_blob.name, signature
                    ).apply(
                        lambda args: f"https://{args[0]}.blob.core.windows.net/{args[1]}/{args[2]}?{args[3]}"
                    ),
                ),
                azure.web.NameValuePairArgs(
                    name="AzureWebJobsStorage",
                    value=pulumi.Output.all(
                        account.name, primary_key
                    ).apply(
                        lambda args: f"DefaultEndpointsProtocol=https;AccountName={args[0]};AccountKey={args[1]};EndpointSuffix=core.windows.net",
                    ),
                ),
                azure.web.NameValuePairArgs(
                    name="AzureWebJobsFeatureFlags",
                    value="EnableWorkerIndexing"
                ),
                azure.web.NameValuePairArgs(
                    name="ENABLE_ORYX_BUILD",
                    value="true",
                ),
                 azure.web.NameValuePairArgs(
                    name="SCM_DO_BUILD_DURING_DEPLOYMENT",
                    value="true",
                ),            
            ],
            cors=azure.web.CorsSettingsArgs(
                allowed_origins=["*"],
            ),
        ),
    ) # create function app

And the function app:

import azure.functions as func
import logging
import custom_package

app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS)


@app.function_name(name="name")
@app.route(route="route/{arg1}/{arg2}", auth_level=func.AuthLevel.ANONYMOUS, methods=[func.HttpMethod.POST])
async def example(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('HTTP trigger function')
    res = custom_package.custom_function(arg1, arg2)    
    return func.HttpResponse(f"Success: {res}", status_code=200)

Are there any good practices to follow when you want to add custom functionality to functions without having to use additional routes or blueprints? The problem has been discussed but looks like there's no certain solution from Azure. Thanks!

1

There are 1 best solutions below

3
Naveen Sharma On
  1. To import custom packages in Python model V2. you can simply create a file with any name <filename>.py and import it in function_app.py using this from <filename> import <user defined function>.

  2. To Import custom packages when you have named it __main__.py in the function_app.py. You need to add the __main__.py file inside a folder and import it using this from <folder name> import __main__ and call the function inside package using __main__.<user defined function>.

Note:

Make sure the <filename> or <foldername> or any file name which is important for your code is not mentioned in the funcignore file when deploying to azure.

Packages installed with pip such as pymongo as you mentioned in the comment, you need to mention it in the requirements.txt file. all the packages which are mentioned in the requiremnets.txt are installed in the azure function during deployment. which helps you function to run properly using required addition packages require in the main code.

Example:
enter image description here

OUTPUT:

  1. using <filename>.py

# My Directory
enter image description here

custom_package.py:

def get_data():
    result= "this message is from the custom package"
    return result

function_app.py

import azure.functions as func
from custom_package import get_data
import logging

app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS)

@app.route(route="http_trigger")
def http_trigger(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')

    return func.HttpResponse(f"Hello, Vivek. This HTTP triggered function executed successfully. \n  \n {get_data()}")

enter image description here

enter image description here

enter image description here

  1. using __main__.py file name.

**#My Directory
enter image description here

__main__.py:

def get_data():
    result= "this message is from the custom package"
    return result

function_app.py

import azure.functions as func
from custom import  __main__
import logging

app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS)

@app.route(route="http_trigger")
def http_trigger(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')

    return func.HttpResponse(f"Hello, Vivek. This HTTP triggered function executed successfully. \n  \n {__main__.get_data()}")

enter image description here