Small, testable helpers around the Microsoft Graph HTTP API. These are the pieces I write every time I hook a Python service into a Microsoft 365 tenant. The official SDKs cover the wire protocol; these helpers cover the awkward corners the SDK leaves to you.
- Fuzzy contact lookup. Business users write names, not object IDs. Given
"felipe delgado", find the right Graph user even if the display name in the tenant is"Juan Felipe Delgado Quevedo". - SharePoint uploads that survive real files. Wraps Microsoft's upload session protocol so files above the simple-upload limit go through cleanly. Retries on transient chunk failures, resumable.
- Paginated iteration made honest. Graph pages with
@odata.nextLink. A helper iterator that yields entities one by one so you can process 50k transcripts without loading them all into memory. - Bot Framework image ingestion. Downloads attachments from a Teams message with the right bearer token, saves them with a real extension based on content-type, and skips files above a configurable size cap.
Every helper takes an HTTP client as a Protocol so you can inject a fake in tests. Nothing here talks to the network by itself.
- Not a full Microsoft Graph SDK. Use
msgraph-sdkormsalfor authentication and full API coverage. - Not opinionated about auth. You pass in an already-authenticated HTTP client. Client credentials, device code, or delegated OAuth all work.
- Python 3.11 or later.
httpxfor the example clients (helpers themselves accept any client that matches theHttpClientProtocol).
pip install -e .from ms_graph_helpers import GraphClient, ContactFinder
graph = GraphClient(http_client=my_authenticated_httpx_client, base_url="https://graph.microsoft.com/v1.0")
finder = ContactFinder(graph)
user = finder.find("felipe delgado")
if user is None:
raise ValueError("No confident match")
print(user["id"], user["displayName"], user["userPrincipalName"])| Helper | File | Purpose |
|---|---|---|
GraphClient |
client.py |
Thin wrapper around any authenticated HTTP client with retries and pagination. |
ContactFinder |
contacts.py |
Accent- and case-insensitive fuzzy matcher against /users. Returns a single best match or None when the confidence gap is too small. |
SharePointUploader |
sharepoint.py |
Upload files through createUploadSession. Chunked, resumable, streams from disk. |
TranscriptIterator |
transcripts.py |
Iterates Teams call transcripts across pages with a since filter for delta polling. |
BotAttachmentDownloader |
bot_attachments.py |
Downloads Bot Framework image attachments, resolves the real extension from content-type, enforces a size cap. |
- HTTP client is injected. No SDK dependency. Tests use a scripted fake client. Production uses
httpx.Clientorrequests.Session, whichever your app already carries. - Pagination is a generator.
TranscriptIteratoryields one entity at a time. Callers decide when to stop. This is how you process a tenant's worth of transcripts without OOMing. - Contact fuzzy match returns
Noneon tie. If two users match with similar scores, no result is returned. Better to raise for a human than to silently pick the wrong Juan. - Attachment extension mapping is explicit.
content-type: image/jpegmaps to.jpg. Never.bin. The mapping is a table, not a call tomimetypes.guess_extension()which is unreliable with the extended content types Bot Framework attaches.
- Bot Framework attachment tokens have short expiration. Download the file the moment you receive the message, don't queue the URL for later.
createUploadSessionrequires the site drive item path to already exist. If a folder needs to be created, use the drive item creation endpoint before starting the session./usersreturns paginated results even when there's only one match. The default page size is small; use$top=999for lookups when you know the tenant is not enormous.
pytest tests/Tests use a scripted FakeHttpClient and cover: fuzzy match tie-breaking, pagination termination, attachment extension resolution, and upload session error surfacing.
MIT. See LICENSE.