office365-rest-python-client
A 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
- Two clients, one library —
ClientContextspeaks the SharePoint REST API;GraphClientspeaks Microsoft Graph. - Fluent and deferred — chain
.select(),.filter(),.expand(),.top(),.order_by()and only hit the wire when you callexecute_query(). - Typed models — every entity is a real Python class (
List,File,Team,Message, …), not a dictionary. - Copy-paste examples — a runnable script for every service and scenario under
examples/.
New here? Pick a client in Choose your client, then jump straight to a runnable snippet in Examples by product.
Table of contents
- Choose your client
- Installation
- Authentication
- Quick start
- Examples by product
- Common patterns
- Async / await
- Dependencies
- Contributing
- Support
Choose your client
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
- SharePoint lists, files, site or tenant administration →
ClientContext - Teams, Outlook, OneDrive, Entra ID, Intune, Purview, reports →
GraphClient - OneDrive files work with either client;
GraphClientis the modern, cross-service path.
Installation
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
Authentication
Modern flows authenticate through Microsoft Entra ID using MSAL. Both clients support delegated (user) and app-only (application) access.
ClientContext (SharePoint)
| 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 |
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
GraphClient (Microsoft Graph)
| 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
Quick start
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()
Examples by product
Every snippet below is lifted from a runnable script. Follow the More → link for the full example and its required permissions.
SharePoint (ClientContext)
# 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 |
OneDrive
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()
Teams
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()
Outlook (mail and calendar)
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()
Entra ID
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)
Planner
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)
To Do
for task_list in client.me.todo.lists.get().execute_query():
print(task_list.display_name, len(task_list.tasks.get().execute_query()), "tasks")
OneNote
with open("Sample.html", "rb") as page_html:
page = client.me.onenote.pages.add(presentation_file=page_html).execute_query()
print(page.links.oneNoteWebUrl)
More services — Admin, Reports, Defender, Intune, Purview, Bookings, Communications, Security, Insights, Backup Storage
Microsoft 365 admin
announcement = client.admin.service_announcement
health = announcement.health_overviews.get().execute_query()
issues = announcement.issues.get().execute_query()
messages = announcement.messages.get().execute_query()
Usage reports
report = client.reports.get_email_activity_counts("D30").execute_query()
print(report.value) # CSV payload
Microsoft Defender
alerts = client.security.alerts_v2.top(20).get().execute_query()
for alert in alerts:
print(alert)
Microsoft Intune
devices = client.device_management.managed_devices.get().execute_query()
for device in devices:
print(device.device_name, device.operating_system, device.compliance_state)
Microsoft Purview
labels = client.security.data_security_and_governance.sensitivity_labels.get().execute_query()
for label in labels:
print(label.display_name, label.id)
Microsoft Bookings
businesses = client.solutions.booking_businesses.get().execute_query()
for business in businesses:
print(business.display_name)
Cloud communications
presence = client.users["alex@contoso.onmicrosoft.com"].presence.get().execute_query()
print(presence.availability, presence.activity)
Security (threat intelligence)
host = client.security.threat_intelligence.hosts["contoso.com"].get().execute_query()
reputation = host.reputation.get().execute_query()
print(reputation.properties.get("score"))
Insights
for item in client.me.insights.trending.get().execute_query():
print(item.resource_reference)
Backup Storage
backup = client.solutions.backup_restore
backup.get().execute_query()
print(backup.service_status.status)
Common patterns
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()
Async / await
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.
Dependencies
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]"
Contributing
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.
Support
- Source and examples — github.com/vgrem/office365-rest-python-client
- Documentation — vgrem.github.io/office365-rest-python-client
- Changelog — CHANGELOG.md
- Questions — GitHub Discussions or tag
office365-rest-python-clienton Stack Overflow
If this project saves you time, please consider sponsoring its development. ⭐
License
Released under the MIT License.
Metadata
Release files for office365-rest-python-client 3.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| office365_rest_python_client-3.3.0.tar.gz | 1.8 MB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| office365_rest_python_client-3.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 6.3 MB
Release files / office365_rest_python_client-3.3.0.tar.gz
| Download URL | office365_rest_python_client-3.3.0.tar.gz |
|---|---|
| Size | 1.8 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
92aeabc6fb2561ef7feec810ae260cb6a8b22ee7ea89c163a5fd16c09248cb63
|
|
BLAKE2b-256 checksum How to use checksums |
196cded5ac712278f4324bd7b35bd752b482bfb84ea22733467ca2f8bf0d1875
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / office365_rest_python_client-3.3.0-py3-none-any.whl
| Download URL | office365_rest_python_client-3.3.0-py3-none-any.whl |
|---|---|
| Size | 4.5 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b5ba86ef9deeba1492b7e9e84b7d2a89f198176e7a6a7850622f39fa2daff502
|
|
BLAKE2b-256 checksum How to use checksums |
dd7246073b2ed59c40782e774f507f862ace9816b54d7e18f8cafb80d611d6bd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Security for office365-rest-python-client 3.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.