pythonazureazure-functionsazure-pipelinesdevops

How to fix "1 functions found (Custom) 0 functions loaded" on my Python Azure Function DevOps Pipeline


I'm trying to deploy a Python Azure Function to Azure Portal using an Azure DevOps Build Pipeline. For some reason the code is deployed to the server, but I'm getting a 404 trying to hit the endpoint. I'm getting the an error that says 1 functions found (Custom) 0 functions loaded as well as an error that says ModuleNotFoundError: No module named 'requests' on the server. I've been at this for hours, but no luck. I have the following details pertaining to the error I'm getting (found below). I'm wondering if anyone can please help me troubleshoot to see if I can get this DevOps pipeline to deploy correctly. Please let me know, thank you!

function_app.py:

import logging
import azure.functions as func

from src.api.controllers.etl_controller import ETLController
from src.helpers.configuration_helper import fetch_configurations

# Define your function
app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS)

# Define your routes
@app.route(route="run", methods=["POST"])
def run(req: func.HttpRequest) -> func.HttpResponse:
    # Fetch updated configurations
    fetch_configurations()
    
    # Process the request
    logging.info(f'Processing request: {req.method} {req.url}')
    controller = ETLController()
    return controller.run_elphi_etl(req)

function.json:

{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["post"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
  ]
}

host.json:

{
  "version": "2.0",
  "functionTimeout": "00:30:00",
  "logging": {
    "logLevel": {
      "default": "Debug"
    },
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  }
}

requirements.txt:

asgiref==3.8.1
asn1crypto==1.5.1
azure-appconfiguration==1.6.0
azure-core==1.30.2
azure-core-tracing-opentelemetry==1.0.0b11
azure-functions==1.20.0
azure-identity==1.17.1
azure-keyvault-secrets==4.8.0
azure-monitor-opentelemetry==1.6.0
azure-monitor-opentelemetry-exporter==1.0.0b27
certifi==2024.7.4
cffi==1.16.0
cfgv==3.4.0
charset-normalizer==3.3.2
colorama==0.4.6
coverage==7.5.4
cryptography==42.0.8
debugpy==1.8.2
Deprecated==1.2.14
distlib==0.3.8
filelock==3.15.4
fixedint==0.1.6
flatten-json==0.1.14
identify==2.6.0
idna==3.7
importlib_metadata==8.0.0
iniconfig==2.0.0
isodate==0.6.1
msal==1.30.0
msal-extensions==1.2.0
msrest==0.7.1
nodeenv==1.9.1
oauthlib==3.2.2
opentelemetry-api==1.26.0
opentelemetry-instrumentation==0.47b0
opentelemetry-instrumentation-asgi==0.47b0
opentelemetry-instrumentation-dbapi==0.47b0
opentelemetry-instrumentation-django==0.47b0
opentelemetry-instrumentation-fastapi==0.47b0
opentelemetry-instrumentation-flask==0.47b0
opentelemetry-instrumentation-psycopg2==0.47b0
opentelemetry-instrumentation-requests==0.47b0
opentelemetry-instrumentation-urllib==0.47b0
opentelemetry-instrumentation-urllib3==0.47b0
opentelemetry-instrumentation-wsgi==0.47b0
opentelemetry-resource-detector-azure==0.1.5
opentelemetry-sdk==1.26.0
opentelemetry-semantic-conventions==0.47b0
opentelemetry-util-http==0.47b0
packaging==24.1
platformdirs==4.2.2
pluggy==1.5.0
portalocker==2.10.1
pre-commit==3.7.1
psutil==5.9.8
pycparser==2.22
PyJWT==2.8.0
pyOpenSSL==24.2.1
pytest==8.2.2
pytest-cov==5.0.0
python-dotenv==1.0.1
pytz==2024.1
pywin32==306; sys_platform == 'win32'
PyYAML==6.0.1
requests==2.32.3
requests-oauthlib==2.0.0
six==1.16.0
snowflake-connector-python==3.11.0
sortedcontainers==2.4.0
tomlkit==0.13.0
typing_extensions==4.12.2
urllib3==2.2.2
virtualenv==20.26.3
wrapt==1.16.0
zipp==3.19.2

azure-pipelines-dev.yaml:

trigger:
  branches:
    include:
      - develop

pool:
  vmImage: "ubuntu-latest"

variables:
  PYTHON_VERSION: "3.11.8"
  FUNCTIONAPP_NAME: "fn-elphi-etl-dev"
  SERVICE_CONNECTION: "Elphi-ETL-Dev"
  RESOURCE_GROUP: "fn-elphi-etl-dev"
  PACKAGE_PATH: "$(Build.ArtifactStagingDirectory)/fn_elphi_etl.zip"

steps:
  - task: UsePythonVersion@0
    inputs:
      versionSpec: "$(PYTHON_VERSION)"
      addToPath: true
    displayName: "Install Python $(PYTHON_VERSION)"

  - script: |
      python -m pip install --upgrade pip
    displayName: "Upgrade pip"

  - script: |
      python -m pip install -r requirements.txt
    displayName: "Install dependencies"

  - script: |
      python -m pytest -v --cov=src --cov-report=term --cov-report=html --cov-report=xml:coverage.xml --junitxml=test-results.xml
    displayName: "Run tests with coverage"

  - task: PublishTestResults@2
    inputs:
      testResultsFormat: "JUnit"
      testResultsFiles: "test-results.xml"
      failTaskOnFailedTests: true
      testRunTitle: "Pytest Results"
    displayName: "Publish Pytest Results"

  - script: |
      mkdir -p $(Build.ArtifactStagingDirectory)/src
      shopt -s dotglob
      cp -r * $(Build.ArtifactStagingDirectory)/src/
      ls -l $(Build.ArtifactStagingDirectory)/src
    displayName: "Copy sources and all directories to artifact staging directory and list contents"

  - task: ArchiveFiles@2
    inputs:
      rootFolderOrFile: "$(Build.ArtifactStagingDirectory)/src"
      includeRootFolder: false
      archiveType: "zip"
      archiveFile: "$(PACKAGE_PATH)"
      replaceExistingArchive: true
    displayName: "Archive files"

  - task: PublishPipelineArtifact@1
    inputs:
      targetPath: "$(Build.ArtifactStagingDirectory)/src"
      artifact: "$(FUNCTIONAPP_NAME)"
    displayName: "Publish Python project as artifact"

  - task: PublishCodeCoverageResults@2
    inputs:
      summaryFileLocation: "$(Build.SourcesDirectory)/coverage.xml"
    displayName: "Publish code coverage results"

  - task: AzureFunctionApp@1
    inputs:
      azureSubscription: "$(SERVICE_CONNECTION)"
      appType: "functionAppLinux"
      appName: "$(FUNCTIONAPP_NAME)"
      package: "$(PACKAGE_PATH)"
      runtimeStack: "python|3.11"
      startUpCommand: "func start"
    displayName: "Deploy Azure Function App"

Server logs:

2024-07-26T17:36:54.860 [Information] Starting JobHost
2024-07-26T17:36:54.861 [Information] Starting Host (HostId=fn-elphi-etl-dev, InstanceId=f791376b-5871-4ad4-ab78-47b08af12162, Version=4.34.2.2, ProcessId=26, AppDomainId=1, InDebugMode=True, InDiagnosticMode=False, FunctionsExtensionVersion=~4)
2024-07-26T17:36:54.863 [Information] Loading functions metadata
2024-07-26T17:36:54.869 [Information] Reading functions metadata (Custom)
2024-07-26T17:36:54.871 [Information] 1 functions found (Custom)
2024-07-26T17:36:54.873 [Information] 0 functions loaded
2024-07-26T17:36:54.875 [Debug] FUNCTIONS_WORKER_RUNTIME value: 'python'
2024-07-26T17:36:54.876 [Debug] Adding Function descriptor provider for language python.
2024-07-26T17:36:54.876 [Debug] Creating function descriptors.
2024-07-26T17:36:54.877 [Debug] Function descriptors created.
2024-07-26T17:36:54.878 [Debug] Placeholder mode is enabled: False
2024-07-26T17:36:54.878 [Debug] RpcFunctionInvocationDispatcher received no functions
2024-07-26T17:36:54.878 [Information] Generating 0 job function(s)
2024-07-26T17:36:54.885 [Warning] No job functions found. Try making your job classes and methods public. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).
2024-07-26T17:36:54.888 [Information] Initializing function HTTP routes
2024-07-26T21:00:17.863 [Debug] Hosting starting
2024-07-26T21:00:17.950 [Information] Traceback (most recent call last):
2024-07-26T21:00:17.950 [Information] File "/azure-functions-host/workers/python/3.11/LINUX/X64/azure_functions_worker/utils/wrappers.py", line 44, in call
2024-07-26T21:00:17.950 [Information] return func(*args, **kwargs)
2024-07-26T21:00:17.950 [Information] ^^^^^^^^^^^^^^^^^^^^^
2024-07-26T21:00:17.950 [Information] File "/azure-functions-host/workers/python/3.11/LINUX/X64/azure_functions_worker/loader.py", line 238, in index_function_app
2024-07-26T21:00:17.950 [Information] imported_module = importlib.import_module(module_name)
2024-07-26T21:00:17.950 [Information] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2024-07-26T21:00:17.950 [Information] File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module
2024-07-26T21:00:17.950 [Information] return _bootstrap._gcd_import(name[level:], package, level)
2024-07-26T21:00:17.950 [Information] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2024-07-26T21:00:17.950 [Information] File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
2024-07-26T21:00:17.950 [Information] File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
2024-07-26T21:00:17.950 [Information] File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
2024-07-26T21:00:17.950 [Information] File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
2024-07-26T21:00:17.950 [Information] File "<frozen importlib._bootstrap_external>", line 940, in exec_module
2024-07-26T21:00:17.950 [Information] File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
2024-07-26T21:00:17.950 [Information] File "/home/site/wwwroot/function_app.py", line 4, in <module>
2024-07-26T21:00:17.950 [Information] from src.api.controllers.etl_controller import ETLController
2024-07-26T21:00:17.950 [Information] File "/home/site/wwwroot/src/api/controllers/etl_controller.py", line 6, in <module>
2024-07-26T21:00:17.950 [Information] from src.api.services.etl_service import ETLService
2024-07-26T21:00:17.950 [Information] File "/home/site/wwwroot/src/api/services/etl_service.py", line 4, in <module>
2024-07-26T21:00:17.950 [Information] from src.api.services.elphi_service import ElphiService
2024-07-26T21:00:17.950 [Information] File "/home/site/wwwroot/src/api/services/elphi_service.py", line 3, in <module>
2024-07-26T21:00:17.950 [Information] from src.clients.elphi_client import ElphiClient
2024-07-26T21:00:17.950 [Information] File "/home/site/wwwroot/src/clients/elphi_client.py", line 3, in <module>
2024-07-26T21:00:17.950 [Information] import requests
2024-07-26T21:00:17.950 [Error] ModuleNotFoundError: No module named 'requests'

And here's the file structure on the server post-deploy:

File Structure on Server

Also, if I run func azure functionapp publish fn-elphi-etl-dev --build remote on my local machine it works correctly and I get the following folder structure instead:

Working folder structure


Solution

  • Based on your screenshots, when you deploy the function app on your local machine, it contains .python_packages folder. The required packages(include request package) will be saved in this folder.

    But when you deploy the function app via Azure Pipelines, it doesn't contain the .python_packages. In this case, the packages can not be found when running the azure function.

    To solve this issue, you can add --target argument to pip install command to force the packages saving in the correct folder and make sure that the folder can be included in the zip package:

    For example:

    python -m pip install --target="./.python_packages/lib/site-packages" -r requirements.txt
    

    Pipeline task:

      - script: |
          python -m pip install --target="./.python_packages/lib/site-packages" -r requirements.txt
        displayName: "Install dependencies"
    

    For more detailed info, you can refer to this doc: Example YAML build pipelines