Build a Python Azure Function to Connect with SharePoint Online via Microsoft Graph API

Introduction

Hi friends, recently I have been learning python for GenAI applications development and I thought of sharing some of the information related to Python with SharePoint. This article walks through creating a Python-based Azure Function that connects to SharePoint Online using Microsoft Graph API. It includes best practices, app registration, secure settings, virtual environment setup, local testing, and deployment instructions.

Part 2 – Secure Python Azure Function Using Azure Key Vault and Managed Identity

🧱 Prerequisites

📥 Step 1: Install Azure Functions Core Tools

Windows

npm i -g azure-functions-core-tools@4 --unsafe-perm true

Requires Node.js and npm.

macOS (Homebrew)

brew tap azure/functions
brew install azure-functions-core-tools@4

Linux (APT)

curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $(lsb_release -cs) main" > /etc/apt/sources.list.d/azure-cli.list'
sudo apt-get update
sudo apt-get install azure-functions-core-tools-4

More: Install Azure Functions Core Tools

🐍 Step 2: Install Python 3.9.x & Create Virtual Environment

Windows

  1. Download Python 3.9 from https://www.python.org/downloads/release/python-3913/
  2. Install to C:\Python39 without adding to PATH.
C:\Python39\python.exe -m venv .venv
.venv\Scripts\activate

macOS/Linux with pyenv

brew install pyenv
pyenv install 3.9.13
pyenv shell 3.9.13
python -m venv .venv
source .venv/bin/activate

Verify

python --version  # should be 3.9.x

🚀 Step 3: Create Python Azure Function Project

func init sharepointfunc --python
cd sharepointfunc
func new --name GetSharePointData --template "HTTP trigger" --authlevel "function"

🧠 Sample Python Code: __init__.py

import os
import logging
import requests
import azure.functions as func

GRAPH_API = "https://graph.microsoft.com/v1.0"

def get_access_token():
    tenant_id = os.environ['TENANT_ID']
    client_id = os.environ['CLIENT_ID']
    client_secret = os.environ['CLIENT_SECRET']

    url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
    data = {
        'grant_type': 'client_credentials',
        'client_id': client_id,
        'client_secret': client_secret,
        'scope': 'https://graph.microsoft.com/.default'
    }
    response = requests.post(url, data=data)
    response.raise_for_status()
    return response.json()['access_token']

def get_site_id(token, hostname, site_path):
    if site_path == '/':
        url = f"{GRAPH_API}/sites/{hostname}"
    else:
        url = f"{GRAPH_API}/sites/{hostname}:/sites{site_path}"
    headers = {'Authorization': f'Bearer {token}'}
    r = requests.get(url, headers=headers)
    r.raise_for_status()
    return r.json()['id']

def get_list_id(token, site_id, list_name):
    url = f"{GRAPH_API}/sites/{site_id}/lists/{list_name}"
    headers = {'Authorization': f'Bearer {token}'}
    r = requests.get(url, headers=headers)
    r.raise_for_status()
    return r.json()['id']

def get_list_items(token, site_id, list_id):
    url = f"{GRAPH_API}/sites/{site_id}/lists/{list_id}/items?$top=10&expand=fields"
    headers = {'Authorization': f'Bearer {token}'}
    r = requests.get(url, headers=headers)
    r.raise_for_status()
    return r.json()

def main(req: func.HttpRequest) -> func.HttpResponse:
    try:
        token = get_access_token()
        hostname = req.params.get('hostname')  # e.g., contoso.sharepoint.com
        site_path = req.params.get('sitepath') or '/'
        list_name = req.params.get('listname')

        site_id = get_site_id(token, hostname, site_path)
        list_id = get_list_id(token, site_id, list_name)
        items = get_list_items(token, site_id, list_id)

        return func.HttpResponse(
            body=str(items),
            status_code=200,
            mimetype="application/json"
        )

    except Exception as e:
        logging.exception("Error occurred")
        return func.HttpResponse(str(e), status_code=500)

The above code is a sample which authenticates to SharePoint online using the Azure App and reads the list items from the given list. The settings are maintained in the local.settings.json. You will get some of the settings value from the next step on Azure App Registration

🔐 Step 4: Register App in Azure AD for Microsoft Graph

  1. Go to Azure Portal
  2. Azure Active Directory → App Registrations → New Registration
  3. Name: SharePointGraphReader
  4. Redirect URI: http://localhost
  5. Save Application (client) IDDirectory (tenant) ID

API Permissions

  • Microsoft Graph
    • Delegated: Sites.Read.AllSites.ReadWrite.All

Click “Grant Admin Consent” after adding.

Certificates & Secrets

  • Create a new Client Secret → Copy the value securely.

🧪 Step 5: Test Azure Function Locally

Ensure the virtual environment is active:

source .venv/bin/activate  # Linux/macOS
.venv\Scripts\activate    # Windows

Run:

func start

Test via cURL

curl http://localhost:7071/api/GetSharePointData

You can test using the curl command or directly browse the URL in the browser which list down the list items based on the site url and lists you had configured in the local.settings.json file.

☁️ Step 6: Create Azure Function App for Python (Linux Required)

⚠️ Python is supported only on Linux-based Function Apps. Use the following:

# Login to Azure
az login

# Set variables
RESOURCE_GROUP="MyResourceGroup"
STORAGE_ACCOUNT="mystoragefunc123"
FUNCTION_APP="sharepoint-func-app"
LOCATION="eastus"

# Create resource group
az group create --name $RESOURCE_GROUP --location $LOCATION

# Create storage account (name must be globally unique)
az storage account create --name $STORAGE_ACCOUNT --location $LOCATION --resource-group $RESOURCE_GROUP --sku Standard_LRS

# Create function app on Linux (REQUIRED for Python)
az functionapp create \
  --resource-group $RESOURCE_GROUP \
  --consumption-plan-location $LOCATION \
  --runtime python \
  --runtime-version 3.9 \
  --functions-version 4 \
  --name $FUNCTION_APP \
  --storage-account $STORAGE_ACCOUNT \
  --os-type Linux

The above command will create the resource group, storage account and then will create the function app for deploying our function. You can also map your existing resource group and storage account and ran only the Create Function App command.

🔐 Step 7: Add App Settings for Secrets

az functionapp config appsettings set \
  --name sp-python-func-app-zip \
  --resource-group rgpythonwo \
  --settings \
    CLIENT_ID="" \
    CLIENT_SECRET="" \
    TENANT_ID="" \
    SCOPE="https://graph.microsoft.com/.default" \
    SHAREPOINT_HOSTNAME="<tenant name>.sharepoint.com" \
    SHAREPOINT_SITE_PATH="" \
    SHAREPOINT_LIST_NAME="FAQCategories"

📤 Step 8: Deploy Function App to Azure

Make sure you’re in the root of your Azure Function project:

func azure functionapp publish $FUNCTION_APP

This command will package your code and deploy it to the specified Function App in Azure.

✅ Step 9: Test Deployed Azure Function

Once deployed, your function is live. Use this URL format to call the function:

https://<your-function-name>.azurewebsites.net/api/GetSharePointData?code=<function-key>

You can get the function key from Azure Portal → Function App → Functions → GetSharePointData → Get Function URL

Conclussion

I hope I was able to share some information about communicating to SharePoint via Python code and then to deploy as a Azure Function app. In future, lets see what are all the best practices that we can implement to make this func app more secure, robust and even work for large lists maintaining the consumption plan and much more.