pythonazuremicrosoft-graph-api

Reading Excel file returns 'Invalid Request' error


My goal is to use Python to read and write to an Excel file in SharePoint that other users will accessing. I developed a solution to a problem on my local machine using Pandas to read and xlWings to write. Now I'm trying to move it to SharePoint for others to use.

The solution is a non-computational application of Excel, it's essentialy to track positions of items so there will be many instances of many empty cells when the file is read. I will need to read the range A1:V20, here's what the sheet looks like: enter image description here

I tried various url changes based off Microsoft's guide here, but still can't get results.

Here are the permissions my application has: enter image description here

I've read some tutorials/answers to questions (i.e. here and here) and can authenticate. I've updated my url based off this answer, but no luck.

Here's the error message I am getting:

{
    "error": {
        "code": "invalidRequest",
        "message": "Invalid request"
    }
}
'value'

Previously I had the url ending in /workbook/tables('Plant_3') based off this answer but I was getting the following error:

"error": {
    "code": "BadRequest",
    "message": "Open navigation properties are not supported on OpenTypes. Property name: 'tables'.",

I beleive that error was because my file has no tables and the url was trying to link to that object/collection.

Here's my code (updated per comments):

from msal import ConfidentialClientApplication
import requests
import json
import pandas as pd
import configparser

config = configparser.ConfigParser()
config.read('config.ini')

client_id = config['entra_auth']['client_id']
client_secret = config['entra_auth']['client_secret']
tenant_id = config['entra_auth']['tenant_id']

msal_scope = ['https://graph.microsoft.com/.default']
msal_app = ConfidentialClientApplication(client_id=client_id,
                                         authority=f"https://login.microsoftonline.com/{tenant_id}",
                                         client_credential=client_secret, )
result = msal_app.acquire_token_silent(scopes=msal_scope,
                                       account=None)
if not result:
    result = msal_app.acquire_token_for_client(scopes=msal_scope)
if 'access_token' in result:
    access_token = result['access_token']
else:
    raise Exception("Failed to acquire token")

headers = {'Authorization': f'Bearer {access_token}'}
site_id = config['site']['site_id']  # https://www.powertechtips.com/check-site-id-sharepoint/
# https://answers.microsoft.com/en-us/msoffice/forum/all/how-can-i-find-the-library-id-on-our-sharepoint/701e68f3-954f-490c-b3cb-ceb8bd5601d1
document_library_id = config['site']['document_library_id']
doc_id = config['site']['doc_id']  # from document details

# create url
url = f"https://graph.microsoft.com/v1.0/sites/{site_id}/drives/{document_library_id}/items/{doc_id}:/workbook/worksheets('Plant_3')/range(address='A1:V20')"

# Make a GET request to the Microsoft Graph API to read the Excel file as a pandas dataframe
response = requests.get(url, headers=headers)
try:  # how I want it to go
    data = response.json().get("value", [])
    # if data found, convert to dataframe.
    if data:
        df = pd.DataFrame(data)
        print(df)
    else:
        print("No data")
except Exception as e:
    print(f"Error: {e}")

Solution

  • I created one app registration and added same API permissions as you like this:

    enter image description here

    In SharePoint, I have one excel file named test.xlsx with below data:

    enter image description here

    Initially, I too got same error when I ran your code to retrieve above excel file data:

    enter image description here

    To resolve the error, I ran below modified python code and got the response with excel data successfully as below:

    from msal import ConfidentialClientApplication
    import requests
    import pandas as pd
    import configparser
    
    # Read the config file for credentials and details
    config = configparser.ConfigParser()
    config.read('config.ini')
    
    client_id = config['entra_auth']['client_id']
    client_secret = config['entra_auth']['client_secret']
    tenant_id = config['entra_auth']['tenant_id']
    
    # Set up Microsoft Graph authentication
    msal_scope = ['https://graph.microsoft.com/.default']
    msal_app = ConfidentialClientApplication(client_id=client_id,
                                             authority=f"https://login.microsoftonline.com/{tenant_id}",
                                             client_credential=client_secret)
    result = msal_app.acquire_token_silent(scopes=msal_scope, account=None)
    if not result:
        result = msal_app.acquire_token_for_client(scopes=msal_scope)
    
    if 'access_token' in result:
        access_token = result['access_token']
    else:
        raise Exception("Failed to acquire token")
    
    # Prepare request headers with the acquired access token
    headers = {'Authorization': f'Bearer {access_token}'}
    
    # Get necessary configuration data for the SharePoint file
    site_id = config['site']['site_id'] 
    document_library_id = config['site']['document_library_id']
    doc_id = config['site']['doc_id']
    
    # Use 'usedRange' to automatically detect the range with data
    url = f"https://graph.microsoft.com/v1.0/sites/{site_id}/drives/{document_library_id}/items/{doc_id}/workbook/worksheets('Sheet1')/usedRange"
    
    # Make a GET request to Microsoft Graph API to read the worksheet range
    response = requests.get(url, headers=headers)
    
    # Extract only the 'values' part of the response
    try:
        data = response.json().get('values', [])
        
        # If data is found, convert it to a pandas DataFrame and print it
        if data:
            df = pd.DataFrame(data[1:], columns=data[0])  # First row as column headers
            print(df)
        else:
            print("No data found.")
    except Exception as e:
        print(f"Error: {e}")
    

    Response:

    enter image description here

    UPDATE:

    For writing new data into excel file using Graph API in Python, I used below modified code and got response like this:

    from msal import ConfidentialClientApplication
    import requests
    import pandas as pd
    import configparser
    import json
    
    config = configparser.ConfigParser()
    config.read('config.ini')
    
    client_id = config['entra_auth']['client_id']
    client_secret = config['entra_auth']['client_secret']
    tenant_id = config['entra_auth']['tenant_id']
    
    msal_scope = ['https://graph.microsoft.com/.default']
    msal_app = ConfidentialClientApplication(client_id=client_id,
                                             authority=f"https://login.microsoftonline.com/{tenant_id}",
                                             client_credential=client_secret)
    result = msal_app.acquire_token_silent(scopes=msal_scope, account=None)
    if not result:
        result = msal_app.acquire_token_for_client(scopes=msal_scope)
    
    if 'access_token' in result:
        access_token = result['access_token']
    else:
        raise Exception("Failed to acquire token")
    
    headers = {'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json'}
    
    site_id = config['site']['site_id'] 
    document_library_id = config['site']['document_library_id']
    doc_id = config['site']['doc_id']
    
    # Define the new data you want to write (e.g., add new rows of data)
    new_data = [
        ["Venkat", "8k"],
        ["Arko", "4k"]
    ]
    
    body = {
        "values": new_data
    }
    
    # Here writing to A6:B7 -> 2 columns and 2 rows (Venkat and Arko)
    url_write = f"https://graph.microsoft.com/v1.0/sites/{site_id}/drives/{document_library_id}/items/{doc_id}/workbook/worksheets('Sheet1')/range(address='A6:B7')"
    
    response_write = requests.patch(url_write, headers=headers, data=json.dumps(body))
    
    if response_write.status_code == 200:
        print("Data successfully written to Excel.")
    else:
        print(f"Error writing data: {response_write.text}")
        exit()
    
    # Use 'usedRange' to automatically detect the range with data (including the newly added data)
    url_read = f"https://graph.microsoft.com/v1.0/sites/{site_id}/drives/{document_library_id}/items/{doc_id}/workbook/worksheets('Sheet1')/usedRange"
    
    response = requests.get(url_read, headers=headers)
    
    try:
        data = response.json().get('values', [])
        
        # If data is found, convert it to a pandas DataFrame and print it
        if data:
            df = pd.DataFrame(data[1:], columns=data[0])  # First row as column headers
            print("Updated Excel Data:")
            print(df)
        else:
            print("No data found.")
    except Exception as e:
        print(f"Error while reading: {e}")
    

    Response:

    enter image description here