Programmatically Editing Excel Files on SharePoint Online with Python
Introduction
Editing Excel files stored on SharePoint Online (SPO) programmatically is a powerful capability for automating business workflows, generating reports, and integrating data from web applications. Unlike traditional methods that rely on Microsoft Excel, COM automation, or macros, the Microsoft Graph API enables direct, secure, and scalable access to Excel workbooks in the cloud—without requiring Excel to be installed or running on the server.
This article demonstrates how to: - Open and edit an XLSX file stored on SharePoint Online using Python and the Microsoft Graph API - Inject data into worksheets, triggering automatic updates to formulas, tables, and charts - Loop through worksheets and extract refreshed charts as image files - Build a web interface for user input
Authentication and Permissions
For authentication setup and certificate-based SPN connection, see my previous blog articles: - Certificate Based Authentication
Required Permissions for SPN:
- Files.ReadWrite.All
(edit/view files)
- Sites.ReadWrite.All
(access SPO sites)
- User.Read
(basic profile)
- offline_access
(refresh tokens)
Once authenticated, you can use the access token to interact with the Excel file on SPO.
Connecting to the Excel File on SPO
The following code sets up the workbook endpoint and session management:
DRIVE_ID = '...'
ITEM_ID = '...'
GRAPH_BASE = f"{AZURE_GRAPH_V1}drives/{DRIVE_ID}/items/{ITEM_ID}/workbook"
def headers(token, session_id=None):
h = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
if session_id:
h["workbook-session-id"] = session_id
return h
Creating and Closing Workbook Sessions
Sessions ensure your changes are saved and visible to all users. Always close the session after editing.
def create_session(token, persist=True):
url = f"{GRAPH_BASE}/createSession"
body = {"persistChanges": persist}
r = requests.post(url, headers=headers(token), json=body)
r.raise_for_status()
return r.json()["id"]
def close_session(token, session_id):
url = f"{GRAPH_BASE}/closeSession"
try:
r = requests.post(url, headers=headers(token, session_id))
if r.status_code == 204:
print("✅ Workbook session closed successfully.")
else:
print(f"⚠️ Could not close session cleanly: {r.status_code} {r.text}")
except Exception as e:
print(f"⚠️ Error closing session: {e}")
Listing Worksheets and Charts
You can enumerate all worksheets and charts in the workbook:
def list_worksheets(token, session_id):
url = f"{GRAPH_BASE}/worksheets"
r = requests.get(url, headers=headers(token, session_id))
r.raise_for_status()
return r.json().get("value", [])
def list_charts(token, session_id, sheet_name):
url = f"{GRAPH_BASE}/worksheets('{sheet_name}')/charts"
r = requests.get(url, headers=headers(token, session_id))
r.raise_for_status()
return r.json().get("value", [])
Injecting Data into Excel Worksheets
You can update any cell in the workbook using the Graph API. This triggers recalculation of formulas, tables, and charts automatically. Here is a full example from common.py
showing how to inject multiple parameters and pump data:
def update_workbook(token, session_id, file_to_process):
# ...load JSON data...
global_params = ['jobPressure', 'driverPower', 'fleetSize', 'pumpUsage', 'engineRPM']
for param in global_params:
if param in data:
param_data = data[param]
value = param_data['value']
worksheet = param_data['worksheet']
cell = param_data['cell']
update_cell(token, session_id, worksheet, cell, value)
# Process pumps array
if 'pumps' in data:
for idx, pump in enumerate(data['pumps']):
# Update pump name
name_data = pump['name']
update_cell(token, session_id, name_data['worksheet'], name_data['cell'], name_data['value'])
# Update pump diameter
diameter_data = pump['diameter']
update_cell(token, session_id, diameter_data['worksheet'], diameter_data['cell'], diameter_data['value'])
Key Points:
- You do not need to open Excel or use macros.
- All formulas, VLOOKUPs, and charts update automatically after cell edits.
- Only .xlsx
files are supported; macro-enabled files (.xlsm
) are not editable via Graph API.
Looping Through Worksheets and Extracting Charts
After updating the workbook, you can loop through all worksheets and extract charts as images:
worksheets = list_worksheets(token, session_id)
for ws in worksheets:
ws_name = ws["name"]
charts = list_charts(token, session_id, ws_name)
if not charts:
print(f"No charts found on worksheet '{ws_name}'")
continue
print(f"Charts on '{ws_name}': {[c['name'] for c in charts]}")
for chart in charts:
chart_name = chart["name"]
try:
img_bytes = get_chart_image(token, session_id, ws_name, chart_name)
fname = os.path.join(PROCESSED_PATH, f"{graph_files_prefix}_{ws_name}_{chart_name.replace(' ', '_')}.png")
with open(fname, "wb") as f:
f.write(img_bytes)
print(f" ✔ Saved chart '{chart_name}' as {fname}")
except Exception as e:
print(f" ⚠ Failed to get chart '{chart_name}': {e}")
Chart Extraction Function:
def get_chart_image(token, session_id, sheet, chart_name):
cname = quote(chart_name, safe="")
url = f"{GRAPH_BASE}/worksheets('{sheet}')/charts('{cname}')/image(width=0,height=0,fittingMode='fit')"
r = requests.get(url, headers=headers(token, session_id))
r.raise_for_status()
b64 = r.json()["value"]
return base64.b64decode(b64)
End-to-End Workflow Example
Here is a simplified workflow for processing pump comparison requests:
def process_pump_comparison_requests():
unprocessed_files = fetch_unprocessed_pump_comparisons_requests()
for unprocessed_file in unprocessed_files:
token = get_access_token_API_Access_AAD()
session_id = create_session(token)
graph_files_prefix, email_recipients, email_recipients_bcc = update_workbook(token, session_id, unprocessed_file)
OUTPUT_PDF = os.path.join(PROCESSED_PATH, f"{graph_files_prefix}_Pump_Comparison.pdf")
worksheets = list_worksheets(token, session_id)
print("Worksheets found:", [ws["name"] for ws in worksheets])
# Loop through worksheets and extract charts
for ws in worksheets:
ws_name = ws["name"]
charts = list_charts(token, session_id, ws_name)
if not charts:
print(f"No charts found on worksheet '{ws_name}'")
continue
print(f"Charts on '{ws_name}': {[c['name'] for c in charts]}")
for chart in charts:
chart_name = chart["name"]
try:
img_bytes = get_chart_image(token, session_id, ws_name, chart_name)
fname = os.path.join(PROCESSED_PATH, f"{graph_files_prefix}_{ws_name}_{chart_name.replace(' ', '_')}.png")
with open(fname, "wb") as f:
f.write(img_bytes)
print(f" ✔ Saved chart '{chart_name}' as {fname}")
except Exception as e:
print(f" ⚠ Failed to get chart '{chart_name}': {e}")
close_session(token, session_id)
Visualizing Results: Generating PDFs with Refreshed Charts
After updating the Excel file and extracting charts, you can use ReportLab to generate visually appealing PDF reports. The code in common.py
demonstrates how to:
- Render worksheet data as tables
- Insert chart images
- Format and style the PDF output
Example: PDF Generation
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet
doc = SimpleDocTemplate(
OUTPUT_PDF,
pagesize=landscape(letter),
leftMargin=6,
rightMargin=6,
topMargin=6,
bottomMargin=6
)
elements = []
styles = getSampleStyleSheet()
# Add worksheet data as table, add charts as images
# ...see common.py for full implementation...
Limitations and Considerations
- Macro-Enabled Files: The Graph API cannot edit
.xlsm
files. Macros are not executed or updated. - Security: All operations require Azure AD authentication and appropriate permissions.
- Concurrency: Use workbook sessions to avoid conflicts and ensure changes are saved.
Reference: Why can't macro-enabled Excel files be edited via Graph API?
Conclusion
By leveraging the Microsoft Graph API, you can automate Excel file editing and reporting on SharePoint Online—without relying on Excel, COM automation, or macros. This approach is secure, scalable, and works across platforms.
Key Takeaways:
- Use Graph API for direct, cloud-native Excel automation
- Only .xlsx
files are supported (no macros)
- All formulas, tables, and charts update automatically
- Extract charts as images for reporting
- Integrate with web applications for seamless user input