Microsoft 365 & Microsoft Graph Library for Python
See the codeA Python client library for Microsoft 365, the SharePoint REST API and Microsoft Graph.
Python 3.8+ · typed models · fluent queries · deferred execution · 700+ runnable examples
ClientContext speaks the SharePoint REST API; GraphClient speaks Microsoft Graph..select(), .filter(), .expand(), .top(), .order_by() and only hit the wire when you call execute_query().List, File, Team, Message, …), not a dictionary.examples/.New here? Pick a client in Choose your client, then jump straight to a runnable snippet in Examples by product.
The library ships two clients. Pick the one that matches the API you need.
ClientContext | GraphClient | |
|---|---|---|
| Talks to | SharePoint REST API v1 | Microsoft Graph (graph.microsoft.com) |
| Entry point | ClientContext("https://contoso.sharepoint.com/sites/team") | GraphClient(tenant="contoso.onmicrosoft.com") |
| Best for | SharePoint lists, items, files, folders, search, permissions, site and tenant administration, taxonomy, webhooks | Outlook, OneDrive, Teams, OneNote, Planner, To Do, Entra ID, Intune, Purview, Bookings, reports, security… |
| SharePoint | Full fidelity | Partial — prefer ClientContext for SharePoint-only work |
| Typed models | List, ListItem, File, Folder, Web, Site, User … | DriveItem, Team, Message, Event, User, Group, PlannerTask … |
| Auth | certificate, interactive, device flow, cookies, NTLM (on-prem) | client secret, certificate, interactive, device flow, ROPC |
| Examples | examples/sharepoint/ | examples/ by product |
| API reference | SharePoint REST | Microsoft Graph |
Rule of thumb
ClientContextGraphClientGraphClient is the modern, cross-service path.pip install office365-rest-python-client
With uv:
uv add office365-rest-python-client
Directly from source:
pip install git+https://github.com/vgrem/office365-rest-python-client.git
Modern flows authenticate through Microsoft Entra ID using MSAL. Both clients support delegated (user) and app-only (application) access.
[!IMPORTANT] SharePoint ACS app-only access was retired in April 2026 and SAML-based authentication in May 2026. Use the certificate, interactive or device-code flows below instead — see
examples/sharepoint/auth/modern/and the migration scriptmigrate_from_saml_to_cert.py.
| Flow | Access | Method |
|---|---|---|
| Certificate | App-only | with_client_certificate(tenant, client_id, thumbprint, cert_path=...) |
| Interactive | Delegated (MFA) | with_interactive(tenant, client_id) |
| Device code | Delegated (MFA) | with_device_flow(tenant, client_id) |
| Username / password | Delegated | with_username_and_password(tenant, client_id, username, password) |
| Custom token | Either | with_access_token(token_func) |
| Browser cookies | Delegated | with_cookies(cookie_source) |
| NTLM | On-premises | ClientContext(url, allow_ntlm=True).with_user_credentials(username, password) |
| ACS / SAML | On-premises (legacy) | legacy app-only · legacy SAML |
[!NOTE] SharePoint app-only access requires a certificate. A client secret is not accepted by SharePoint REST API v1 — use
GraphClientfor secret-based app-only. See why a client secret doesn't work for SharePoint.
from office365.sharepoint.client_context import ClientContext
ctx = ClientContext("https://contoso.sharepoint.com/sites/team").with_client_certificate(
"contoso.onmicrosoft.com",
client_id="00000000-0000-0000-0000-000000000000",
thumbprint="AA11BB22CC33DD44EE55FF66AA77BB88CC99DD00",
cert_path="./private.pem",
)
web = ctx.web.get().execute_query()
print(web.title)
Ready-made scripts: certificate · certificate (private key) · custom scopes · interactive · device flow · username/password · access token · cookies · NTLM
| Flow | Access | Method |
|---|---|---|
| Client secret | App-only | with_client_secret(client_id, client_secret) |
| Certificate | App-only | with_certificate(client_id, thumbprint, private_key) |
| Interactive | Delegated (MFA) | with_token_interactive(client_id) |
| Device code | Delegated (MFA) | with_device_flow(client_id) |
| Username / password | Delegated | with_username_and_password(client_id, username, password) |
| Custom token | Either | GraphClient(token_callback, tenant=...) |
from office365.graph_client import GraphClient
client = GraphClient(tenant="contoso.onmicrosoft.com").with_client_secret(
client_id="00000000-0000-0000-0000-000000000000",
client_secret="your-client-secret",
)
users = client.users.get().execute_query()
print(len(users), "users")
Ready-made scripts: client secret · certificate · interactive · device flow · username/password · custom token callback
SharePoint list — read and write items
from office365.sharepoint.client_context import ClientContext
ctx = ClientContext("https://contoso.sharepoint.com/sites/team").with_client_certificate(
"contoso.onmicrosoft.com", client_id, thumbprint, cert_path="./private.pem"
)
tasks = ctx.web.lists.get_by_title("Tasks")
item = tasks.add_item({"Title": "Write the release notes"}).execute_query()
print("Created item:", item.id)
for task in tasks.items.get_all().execute_query():
print(task.properties["Title"])
Microsoft Graph — send an email
from office365.graph_client import GraphClient
client = GraphClient(tenant="contoso.onmicrosoft.com").with_client_secret(client_id, client_secret)
client.me.send_mail(
subject="Hello from Graph API",
body="This email was sent using the Microsoft Graph API.",
to_recipients=["alex@contoso.onmicrosoft.com"],
).execute_query()
Every snippet below is lifted from a runnable script. Follow the More → link for the full example and its required permissions.
# Lists and items
tasks = ctx.web.lists.get_by_title("Tasks")
item = tasks.add_item({"Title": "Ship it", "Status": "Active"}).execute_query()
tasks.items.get_all().execute_query() # or .filter("Status eq 'Active'").get_all()
# Files and folders
folder = ctx.web.get_folder_by_server_relative_url("/sites/team/Shared Documents")
with open("report.pdf", "rb") as f:
file = folder.files.upload(f).execute_query()
with open("report.pdf", "wb") as f:
ctx.web.get_file_by_server_relative_path("/sites/team/Shared Documents/report.pdf").download(f).execute_query()
# Large file (chunked upload session)
with open("video.mp4", "rb") as f:
folder.files.create_upload_session(f, 10 * 1024 * 1024).execute_query()
| Area | Examples |
|---|---|
| Lists and items | lists · listitems · views |
| Files and folders | files · folders |
| Sites and webs | sites · webs · hubsites |
| Search | search |
| Permissions and sharing | permissions · sharing |
| Fields and content types | fields · contenttypes |
| Pages and navigation | pages · navigation |
| Taxonomy and profiles | taxonomy · userprofile |
| Tenant administration | tenant · groups · users |
| Webhooks, migration, advanced | webhooks · migration · advanced |
uploaded = client.me.drive.root.upload_file("report.xlsx").execute_query()
with open("report.xlsx", "wb") as f:
client.me.drive.root.get_by_path("report.xlsx").download(f).execute_query()
team = client.teams.create_and_wait("Contoso Project", "All project collaboration").execute_query()
channel = team.channels.add("Project Chat").execute_query()
channel.messages.add("Hello team!").execute_query()
from datetime import datetime, timedelta, timezone
# Send mail
client.me.send_mail(
subject="Hello from Graph API",
body="This email was sent using the Microsoft Graph API.",
to_recipients=["alex@contoso.onmicrosoft.com"],
).execute_query()
# Create a calendar event
when = datetime.now(timezone.utc) + timedelta(days=1)
client.me.calendar.events.add(
subject="Team Lunch",
body="Let's grab lunch together.",
start=when,
end=when + timedelta(hours=1),
attendees=["alex@contoso.onmicrosoft.com"],
).execute_query()
from office365.directory.users.password_profile import PasswordProfile
from office365.directory.users.profile import UserProfile
profile = UserProfile(
displayName="Alex Wilber",
userPrincipalName="alex@contoso.onmicrosoft.com",
mailNickname="alex",
accountEnabled=True,
passwordProfile=PasswordProfile(password="P@ssw0rd!", forceChangePasswordNextSignIn=True),
)
user = client.users.add(profile).execute_query()
print(user.display_name)
group = client.groups.find_by_name("My Sample Team", required=True).execute_query()
plans = group.planner.plans.get().execute_query()
task = client.planner.tasks.add("Update client list", plans[0].id).execute_query()
print(task.title)
for task_list in client.me.todo.lists.get().execute_query():
print(task_list.display_name, len(task_list.tasks.get().execute_query()), "tasks")
with open("Sample.html", "rb") as page_html:
page = client.me.onenote.pages.add(presentation_file=page_html).execute_query()
print(page.links.oneNoteWebUrl)
announcement = client.admin.service_announcement
health = announcement.health_overviews.get().execute_query()
issues = announcement.issues.get().execute_query()
messages = announcement.messages.get().execute_query()
report = client.reports.get_email_activity_counts("D30").execute_query()
print(report.value) # CSV payload
alerts = client.security.alerts_v2.top(20).get().execute_query()
for alert in alerts:
print(alert)
devices = client.device_management.managed_devices.get().execute_query()
for device in devices:
print(device.device_name, device.operating_system, device.compliance_state)
labels = client.security.data_security_and_governance.sensitivity_labels.get().execute_query()
for label in labels:
print(label.display_name, label.id)
businesses = client.solutions.booking_businesses.get().execute_query()
for business in businesses:
print(business.display_name)
presence = client.users["alex@contoso.onmicrosoft.com"].presence.get().execute_query()
print(presence.availability, presence.activity)
host = client.security.threat_intelligence.hosts["contoso.com"].get().execute_query()
reputation = host.reputation.get().execute_query()
print(reputation.properties.get("score"))
for item in client.me.insights.trending.get().execute_query():
print(item.resource_reference)
backup = client.solutions.backup_restore
backup.get().execute_query()
print(backup.service_status.status)
Deferred execution. Requests are queued and sent only when you call execute_query(). Chain the fluent methods first, then execute once.
items = (
ctx.web.lists.get_by_title("Tasks")
.items.select(["Title", "Status"])
.filter("Status eq 'Active'")
.order_by("Title")
.top(100)
.get()
.execute_query()
)
Addressing resources. Entities are addressed by the key you already know, so you rarely need an opaque id: user principal name, site path or URL, file path, list title. get_by_url/get_by_path on client.sites fetch when you call execute_query(); keyed/UPN forms and driveItem.get_by_path are lazy, so chain .get() first.
user = client.users["ada@contoso.com"].get().execute_query() # a user, by UPN
site = client.sites.get_by_url("https://contoso.sharepoint.com/sites/marketing").execute_query()
file = client.me.drive.root.get_by_path("Reports/Q1.xlsx").get().execute_query()
items = ctx.web.lists.get_by_title("Tasks").items.get().execute_query()
Reading large collections. Use get_all() to follow paging automatically, and page_size to stay under server limits.
items = ctx.web.lists.get_by_title("Orders").items.get_all(page_size=2000).execute_query()
files = (
ctx.web.get_folder_by_server_relative_url("/sites/team/Shared Documents")
.get_files(recursive=True, page_size=2000)
.execute_query()
)
Batching. Dispatch many queued operations in one round trip. Raise concurrency to run batches in parallel; throttled sub-requests are retried individually, honoring Retry-After.
for row in rows:
ctx.web.lists.get_by_title("Contacts").add_item(row)
ctx.execute_batch() # sequential
ctx.execute_batch(concurrency=5) # up to 5 batches in flight
# Graph
for user in new_users:
client.users.add(user)
client.execute_batch(concurrency=5)
Large lists (> 5,000 items). SharePoint refuses filtered/sorted queries on non-indexed columns. The library can tell you what to index and pre-flight the query.
from office365.sharepoint.listitems.caml import Caml, CamlQuery
query = CamlQuery.builder().where(Caml.text("Status").eq("Active")).order_by("ID").row_limit(2000, paged=True).build()
lst.check_query(query) # raises with a clear message if an index is missing
print(lst.index_candidates(query)) # columns worth indexing
lst.ensure_indexed("Status").execute_query() # create the index (builds in the background)
Import and export DataFrames. Optional helpers turn list items into pandas / Polars frames and back.
lst = ctx.web.lists.ensure_list("Orders").execute_query()
df = lst.to_dataframe().execute_query().value
lst.from_dataframe(df, chunksize=100).execute_query()
Every call that reaches the network has an await twin. Builders stay synchronous — only the terminal call changes. No extra dependency is required: by default the blocking HTTP call is handed to a worker thread, so the event loop stays free.
import asyncio
async def main() -> None:
web = await ctx.web.get().execute_query_async()
print(web.title)
asyncio.run(main())
execute_query_async(), execute_batch_async(), get_all_async(), execute_query_parallel_async(), streaming downloads/exports and long-running-operation polling all have async twins, and collections support async for. See the async guide.
Install the core package on its own, or add the extras you need:
| Extra | Adds | Use it for |
|---|---|---|
azure | azure-storage-blob, cryptography | Azure Blob Storage and certificate helpers |
excel | openpyxl | Reading and writing Excel workbooks |
examples | faker | Running the sample scripts |
ntlm | requests-ntlm | On-premises SharePoint with NTLM |
pandas | pandas | to_dataframe() / from_dataframe() |
parquet | pyarrow | Parquet import and export |
duckdb | duckdb | Querying exported data locally |
sql | sqlalchemy | SQL-backed import and export |
notebooks | jupyter, nbformat, jinja2 | Running the notebook samples |
pip install "office365-rest-python-client[pandas,excel]"
Issues and pull requests are welcome — please use the issue tracker for bugs and ideas.
git clone https://github.com/vgrem/office365-rest-python-client.git
cd office365-rest-python-client
uv sync --all-extras
pytest --offline -q
See CONTRIBUTING.md for the full development setup. Run the gate before submitting: ruff check ., ruff format --check ., pyright and pytest --offline -q.
office365-rest-python-client on Stack OverflowIf this project saves you time, please consider sponsoring its development. ⭐
Released under the MIT License.
361 followers · starred Jun 2022
17 followers · starred Dec 2022
23 followers · starred Oct 2025
32 followers · starred Mar 2023
Microsoft 365 & Microsoft Graph Library for Python
See the codeA Python client library for Microsoft 365, the SharePoint REST API and Microsoft Graph.
Python 3.8+ · typed models · fluent queries · deferred execution · 700+ runnable examples
ClientContext speaks the SharePoint REST API; GraphClient speaks Microsoft Graph..select(), .filter(), .expand(), .top(), .order_by() and only hit the wire when you call execute_query().List, File, Team, Message, …), not a dictionary.examples/.New here? Pick a client in Choose your client, then jump straight to a runnable snippet in Examples by product.
The library ships two clients. Pick the one that matches the API you need.
ClientContext | GraphClient | |
|---|---|---|
| Talks to | SharePoint REST API v1 | Microsoft Graph (graph.microsoft.com) |
| Entry point | ClientContext("https://contoso.sharepoint.com/sites/team") | GraphClient(tenant="contoso.onmicrosoft.com") |
| Best for | SharePoint lists, items, files, folders, search, permissions, site and tenant administration, taxonomy, webhooks | Outlook, OneDrive, Teams, OneNote, Planner, To Do, Entra ID, Intune, Purview, Bookings, reports, security… |
| SharePoint | Full fidelity | Partial — prefer ClientContext for SharePoint-only work |
| Typed models | List, ListItem, File, Folder, Web, Site, User … | DriveItem, Team, Message, Event, User, Group, PlannerTask … |
| Auth | certificate, interactive, device flow, cookies, NTLM (on-prem) | client secret, certificate, interactive, device flow, ROPC |
| Examples | examples/sharepoint/ | examples/ by product |
| API reference | SharePoint REST | Microsoft Graph |
Rule of thumb
ClientContextGraphClientGraphClient is the modern, cross-service path.pip install office365-rest-python-client
With uv:
uv add office365-rest-python-client
Directly from source:
pip install git+https://github.com/vgrem/office365-rest-python-client.git
Modern flows authenticate through Microsoft Entra ID using MSAL. Both clients support delegated (user) and app-only (application) access.
[!IMPORTANT] SharePoint ACS app-only access was retired in April 2026 and SAML-based authentication in May 2026. Use the certificate, interactive or device-code flows below instead — see
examples/sharepoint/auth/modern/and the migration scriptmigrate_from_saml_to_cert.py.
| Flow | Access | Method |
|---|---|---|
| Certificate | App-only | with_client_certificate(tenant, client_id, thumbprint, cert_path=...) |
| Interactive | Delegated (MFA) | with_interactive(tenant, client_id) |
| Device code | Delegated (MFA) | with_device_flow(tenant, client_id) |
| Username / password | Delegated | with_username_and_password(tenant, client_id, username, password) |
| Custom token | Either | with_access_token(token_func) |
| Browser cookies | Delegated | with_cookies(cookie_source) |
| NTLM | On-premises | ClientContext(url, allow_ntlm=True).with_user_credentials(username, password) |
| ACS / SAML | On-premises (legacy) | legacy app-only · legacy SAML |
[!NOTE] SharePoint app-only access requires a certificate. A client secret is not accepted by SharePoint REST API v1 — use
GraphClientfor secret-based app-only. See why a client secret doesn't work for SharePoint.
from office365.sharepoint.client_context import ClientContext
ctx = ClientContext("https://contoso.sharepoint.com/sites/team").with_client_certificate(
"contoso.onmicrosoft.com",
client_id="00000000-0000-0000-0000-000000000000",
thumbprint="AA11BB22CC33DD44EE55FF66AA77BB88CC99DD00",
cert_path="./private.pem",
)
web = ctx.web.get().execute_query()
print(web.title)
Ready-made scripts: certificate · certificate (private key) · custom scopes · interactive · device flow · username/password · access token · cookies · NTLM
| Flow | Access | Method |
|---|---|---|
| Client secret | App-only | with_client_secret(client_id, client_secret) |
| Certificate | App-only | with_certificate(client_id, thumbprint, private_key) |
| Interactive | Delegated (MFA) | with_token_interactive(client_id) |
| Device code | Delegated (MFA) | with_device_flow(client_id) |
| Username / password | Delegated | with_username_and_password(client_id, username, password) |
| Custom token | Either | GraphClient(token_callback, tenant=...) |
from office365.graph_client import GraphClient
client = GraphClient(tenant="contoso.onmicrosoft.com").with_client_secret(
client_id="00000000-0000-0000-0000-000000000000",
client_secret="your-client-secret",
)
users = client.users.get().execute_query()
print(len(users), "users")
Ready-made scripts: client secret · certificate · interactive · device flow · username/password · custom token callback
SharePoint list — read and write items
from office365.sharepoint.client_context import ClientContext
ctx = ClientContext("https://contoso.sharepoint.com/sites/team").with_client_certificate(
"contoso.onmicrosoft.com", client_id, thumbprint, cert_path="./private.pem"
)
tasks = ctx.web.lists.get_by_title("Tasks")
item = tasks.add_item({"Title": "Write the release notes"}).execute_query()
print("Created item:", item.id)
for task in tasks.items.get_all().execute_query():
print(task.properties["Title"])
Microsoft Graph — send an email
from office365.graph_client import GraphClient
client = GraphClient(tenant="contoso.onmicrosoft.com").with_client_secret(client_id, client_secret)
client.me.send_mail(
subject="Hello from Graph API",
body="This email was sent using the Microsoft Graph API.",
to_recipients=["alex@contoso.onmicrosoft.com"],
).execute_query()
Every snippet below is lifted from a runnable script. Follow the More → link for the full example and its required permissions.
# Lists and items
tasks = ctx.web.lists.get_by_title("Tasks")
item = tasks.add_item({"Title": "Ship it", "Status": "Active"}).execute_query()
tasks.items.get_all().execute_query() # or .filter("Status eq 'Active'").get_all()
# Files and folders
folder = ctx.web.get_folder_by_server_relative_url("/sites/team/Shared Documents")
with open("report.pdf", "rb") as f:
file = folder.files.upload(f).execute_query()
with open("report.pdf", "wb") as f:
ctx.web.get_file_by_server_relative_path("/sites/team/Shared Documents/report.pdf").download(f).execute_query()
# Large file (chunked upload session)
with open("video.mp4", "rb") as f:
folder.files.create_upload_session(f, 10 * 1024 * 1024).execute_query()
| Area | Examples |
|---|---|
| Lists and items | lists · listitems · views |
| Files and folders | files · folders |
| Sites and webs | sites · webs · hubsites |
| Search | search |
| Permissions and sharing | permissions · sharing |
| Fields and content types | fields · contenttypes |
| Pages and navigation | pages · navigation |
| Taxonomy and profiles | taxonomy · userprofile |
| Tenant administration | tenant · groups · users |
| Webhooks, migration, advanced | webhooks · migration · advanced |
uploaded = client.me.drive.root.upload_file("report.xlsx").execute_query()
with open("report.xlsx", "wb") as f:
client.me.drive.root.get_by_path("report.xlsx").download(f).execute_query()
team = client.teams.create_and_wait("Contoso Project", "All project collaboration").execute_query()
channel = team.channels.add("Project Chat").execute_query()
channel.messages.add("Hello team!").execute_query()
from datetime import datetime, timedelta, timezone
# Send mail
client.me.send_mail(
subject="Hello from Graph API",
body="This email was sent using the Microsoft Graph API.",
to_recipients=["alex@contoso.onmicrosoft.com"],
).execute_query()
# Create a calendar event
when = datetime.now(timezone.utc) + timedelta(days=1)
client.me.calendar.events.add(
subject="Team Lunch",
body="Let's grab lunch together.",
start=when,
end=when + timedelta(hours=1),
attendees=["alex@contoso.onmicrosoft.com"],
).execute_query()
from office365.directory.users.password_profile import PasswordProfile
from office365.directory.users.profile import UserProfile
profile = UserProfile(
displayName="Alex Wilber",
userPrincipalName="alex@contoso.onmicrosoft.com",
mailNickname="alex",
accountEnabled=True,
passwordProfile=PasswordProfile(password="P@ssw0rd!", forceChangePasswordNextSignIn=True),
)
user = client.users.add(profile).execute_query()
print(user.display_name)
group = client.groups.find_by_name("My Sample Team", required=True).execute_query()
plans = group.planner.plans.get().execute_query()
task = client.planner.tasks.add("Update client list", plans[0].id).execute_query()
print(task.title)
for task_list in client.me.todo.lists.get().execute_query():
print(task_list.display_name, len(task_list.tasks.get().execute_query()), "tasks")
with open("Sample.html", "rb") as page_html:
page = client.me.onenote.pages.add(presentation_file=page_html).execute_query()
print(page.links.oneNoteWebUrl)
announcement = client.admin.service_announcement
health = announcement.health_overviews.get().execute_query()
issues = announcement.issues.get().execute_query()
messages = announcement.messages.get().execute_query()
report = client.reports.get_email_activity_counts("D30").execute_query()
print(report.value) # CSV payload
alerts = client.security.alerts_v2.top(20).get().execute_query()
for alert in alerts:
print(alert)
devices = client.device_management.managed_devices.get().execute_query()
for device in devices:
print(device.device_name, device.operating_system, device.compliance_state)
labels = client.security.data_security_and_governance.sensitivity_labels.get().execute_query()
for label in labels:
print(label.display_name, label.id)
businesses = client.solutions.booking_businesses.get().execute_query()
for business in businesses:
print(business.display_name)
presence = client.users["alex@contoso.onmicrosoft.com"].presence.get().execute_query()
print(presence.availability, presence.activity)
host = client.security.threat_intelligence.hosts["contoso.com"].get().execute_query()
reputation = host.reputation.get().execute_query()
print(reputation.properties.get("score"))
for item in client.me.insights.trending.get().execute_query():
print(item.resource_reference)
backup = client.solutions.backup_restore
backup.get().execute_query()
print(backup.service_status.status)
Deferred execution. Requests are queued and sent only when you call execute_query(). Chain the fluent methods first, then execute once.
items = (
ctx.web.lists.get_by_title("Tasks")
.items.select(["Title", "Status"])
.filter("Status eq 'Active'")
.order_by("Title")
.top(100)
.get()
.execute_query()
)
Addressing resources. Entities are addressed by the key you already know, so you rarely need an opaque id: user principal name, site path or URL, file path, list title. get_by_url/get_by_path on client.sites fetch when you call execute_query(); keyed/UPN forms and driveItem.get_by_path are lazy, so chain .get() first.
user = client.users["ada@contoso.com"].get().execute_query() # a user, by UPN
site = client.sites.get_by_url("https://contoso.sharepoint.com/sites/marketing").execute_query()
file = client.me.drive.root.get_by_path("Reports/Q1.xlsx").get().execute_query()
items = ctx.web.lists.get_by_title("Tasks").items.get().execute_query()
Reading large collections. Use get_all() to follow paging automatically, and page_size to stay under server limits.
items = ctx.web.lists.get_by_title("Orders").items.get_all(page_size=2000).execute_query()
files = (
ctx.web.get_folder_by_server_relative_url("/sites/team/Shared Documents")
.get_files(recursive=True, page_size=2000)
.execute_query()
)
Batching. Dispatch many queued operations in one round trip. Raise concurrency to run batches in parallel; throttled sub-requests are retried individually, honoring Retry-After.
for row in rows:
ctx.web.lists.get_by_title("Contacts").add_item(row)
ctx.execute_batch() # sequential
ctx.execute_batch(concurrency=5) # up to 5 batches in flight
# Graph
for user in new_users:
client.users.add(user)
client.execute_batch(concurrency=5)
Large lists (> 5,000 items). SharePoint refuses filtered/sorted queries on non-indexed columns. The library can tell you what to index and pre-flight the query.
from office365.sharepoint.listitems.caml import Caml, CamlQuery
query = CamlQuery.builder().where(Caml.text("Status").eq("Active")).order_by("ID").row_limit(2000, paged=True).build()
lst.check_query(query) # raises with a clear message if an index is missing
print(lst.index_candidates(query)) # columns worth indexing
lst.ensure_indexed("Status").execute_query() # create the index (builds in the background)
Import and export DataFrames. Optional helpers turn list items into pandas / Polars frames and back.
lst = ctx.web.lists.ensure_list("Orders").execute_query()
df = lst.to_dataframe().execute_query().value
lst.from_dataframe(df, chunksize=100).execute_query()
Every call that reaches the network has an await twin. Builders stay synchronous — only the terminal call changes. No extra dependency is required: by default the blocking HTTP call is handed to a worker thread, so the event loop stays free.
import asyncio
async def main() -> None:
web = await ctx.web.get().execute_query_async()
print(web.title)
asyncio.run(main())
execute_query_async(), execute_batch_async(), get_all_async(), execute_query_parallel_async(), streaming downloads/exports and long-running-operation polling all have async twins, and collections support async for. See the async guide.
Install the core package on its own, or add the extras you need:
| Extra | Adds | Use it for |
|---|---|---|
azure | azure-storage-blob, cryptography | Azure Blob Storage and certificate helpers |
excel | openpyxl | Reading and writing Excel workbooks |
examples | faker | Running the sample scripts |
ntlm | requests-ntlm | On-premises SharePoint with NTLM |
pandas | pandas | to_dataframe() / from_dataframe() |
parquet | pyarrow | Parquet import and export |
duckdb | duckdb | Querying exported data locally |
sql | sqlalchemy | SQL-backed import and export |
notebooks | jupyter, nbformat, jinja2 | Running the notebook samples |
pip install "office365-rest-python-client[pandas,excel]"
Issues and pull requests are welcome — please use the issue tracker for bugs and ideas.
git clone https://github.com/vgrem/office365-rest-python-client.git
cd office365-rest-python-client
uv sync --all-extras
pytest --offline -q
See CONTRIBUTING.md for the full development setup. Run the gate before submitting: ruff check ., ruff format --check ., pyright and pytest --offline -q.
office365-rest-python-client on Stack OverflowIf this project saves you time, please consider sponsoring its development. ⭐
Released under the MIT License.
361 followers · starred Jun 2022
17 followers · starred Dec 2022
23 followers · starred Oct 2025
32 followers · starred Mar 2023