Skip to main content

GraphBridge is a lightweight Microsoft Graph client that uses app-only (Azure AD) authentication and streamlines SharePoint site/list operations: metadata retrieval, feature-based queries, CRUD, upsert, and field key encoding/decoding.

Project description

GbSharePoint (GbAuth · GbSite · GbList)

A small Python wrapper to work with Microsoft Graph and SharePoint Lists using app-only authentication (Client Credentials).

The library includes the classes GbAuth, GbSite, GbList and a couple of utilities (deduplicate_dicts, field name encoding/decoding).


Requirements

  • Python ≥ 3.10

  • An app registered in Microsoft Entra ID (Azure AD) with Application Permissions to Microsoft Graph:

    • Read: Sites.Read.All (minimum)
    • Write (CRUD): Sites.ReadWrite.All
    • Alternatively (recommended for least privilege): Sites.Selected + grant site-level access.
  • Admin consent granted for the app permissions.

Installation

pip install azure-identity requests

Key concepts

  • GbAuth: handles credentials and acquires the Graph bearer token (ClientSecretCredential).
  • GbSite: resolves a SharePoint site id from hostname and site_path.
  • GbList: performs list operations (list_items, list_rows, create, update, delete, upload, …).

All requests target the v1.0 Graph endpoint (https://graph.microsoft.com/v1.0).


Quickstart

from gbsharepoint import GbAuth, GbSite, GbList  # or import from your own module file

# 1) App-only auth
auth = GbAuth(
    tenant_id="00000000-0000-0000-0000-000000000000",
    client_id="11111111-1111-1111-1111-111111111111",
    client_secret="YOUR_CLIENT_SECRET"
)

# 2) SharePoint site (Graph uses hostname + site path)
site = GbSite(
    hostname="contoso.sharepoint.com",
    site_path="/sites/Marketing",   # include the leading slash
    gb_auth=auth
)

print("Site ID:", site.site_id)

# 3) SharePoint list
sp_list = GbList(
    list_name="Campaigns 2025",   # display name of the list
    gb_site=site
)

# 4) Read
print("Fields:", sp_list.list_fields)  # e.g. ['Title', 'Status', 'Owner', ...]
rows = sp_list.list_rows               # list of dicts (the item's 'fields' object)
for r in rows:
    print(r["Title"], r.get("Status"))

Usage examples

Read items

rows = sp_list.list_rows
print(len(rows), "rows")
print(rows[0])  # {'Title': 'Campaign A', 'Status': 'Active', ...}

list_fields returns column internal names inferred from the first row. If the list is empty, it returns [].

Create items

new_item = {
    "Title": "New Campaign",
    "Status": "Active",
    "Budget": 5000
}
result = sp_list.create(new_item)

# {'successes': [{'id': '123', 'success': True, 'item': {...}}], 'failures': []}
print(result["successes"])

create() accepts either a single dict or a list of dicts. Each dict is wrapped as {"fields": row} as required by Graph.

Update items (PATCH)

item_id = sp_list.list_ids[0]  # Graph item id (string)
patch = {"Status": "Closed"}

result = sp_list.update(ids=item_id, rows=patch)
# For batch updates: ids=[...], rows=[{...}, {...}]
print(result)

Performs a PATCH to /lists/{list_id}/items/{item_id}/fields with the passed rows dict.

Delete items

to_delete = sp_list.list_ids[:2]
result = sp_list.delete(to_delete)
print("Deleted:", [s["id"] for s in result["successes"]])

Upsert / sync with upload()

# Synchronize the list with the provided rows:
# - if an id exists: update it (or fully replace if force=True)
# - if an id does NOT exist: create a new item
# - if delete=True: remove existing items not present in 'ids'

ids  = ["10", "42"]  # Graph item ids (strings)
rows = [
    {"Title": "Row 10", "Status": "Active"},
    {"Title": "Row 42", "Status": "Closed"}
]

report = sp_list.upload(ids=ids, rows=rows, force=False, delete=False)
print(report)

Important notes on upload()

  • IDs must be Graph item ids (those from sp_list.list_ids).
  • For new items (id not found), the function creates an item and reports new_id in the result.
  • force=True means delete + create (hard replace).
  • delete=True removes items not included in ids.

Filter items with get_items_by_features()

This method evaluates predicates against sp_list.list_items (the full Graph item object that includes the fields sub-object). To filter on list columns, specify predicates nested under fields:

features = [
    {"fields": {"Status": "Active"}},                 # AND within the same dict
    {"fields": {"Owner": "mario.rossi@contoso.com"}}
]
matched_items = sp_list.get_items_by_features(features)
# Returns a de-duplicated list of Graph items (with 'id', 'fields', etc.)
  • The list of dicts in features is combined in OR.
  • Key/value pairs inside a single dict are combined in AND.
  • One level of nesting is supported (e.g., {"fields": {"Category": {"Name": "Premium"}}} if your fields contains nested objects).

Field name encoding/decoding

SharePoint internal names may contain sequences like _x0020_ for spaces. GbList exposes:

  • encode_row(row: dict) -> dict: replaces special characters in key names (' '_x0020_, etc.)
  • decode_row(row: dict) -> dict: reverse operation.

Example:

human = {"Customer name": "ACME", "Close date": "2025-08-01"}
encoded = sp_list.encode_row(human)  # {'Customer_x0020_name': 'ACME', ...}
created = sp_list.create(encoded)

If you already use the correct internal names, you don’t need to encode.


API reference (main properties & methods)

GbAuth

  • token → Graph bearer token (cached).
  • headers{'Authorization': 'Bearer <token>'}

Changing tenant_id, client_id, or client_secret clears cached auth/token.

GbSite

  • site_urlhttps://graph.microsoft.com/v1.0/sites/{hostname}:{site_path}
  • site_data → site JSON (cached)
  • site_idsite_data["id"]

GbList

  • list_urlhttps://graph.microsoft.com/v1.0/sites/{site_id}/lists/{quote(list_name)}
  • list_data → list metadata (cached)
  • list_idlist_data["id"]
  • list_itemsGET {list_url}/items?expand=fields (list of Graph items)
  • list_rows[item["fields"] for item in list_items]
  • list_ids[item["id"] for item in list_items]
  • list_fields→ column names from the first row (or [])

CRUD:

  • create(rows) → POST /items (accepts dict or list of dicts; wraps as {"fields": ...}).
  • update(ids, rows) → PATCH /items/{id}/fields
  • delete(ids) → DELETE /items/{id}
  • upload(ids, rows, force=False, delete=False) → upsert + optional cleanup.

Utilities:

  • get_items_by_features(features) → OR across groups of predicates (AND within each group).
  • encode_row(row), decode_row(row)
  • deduplicate_dicts(list_of_dicts) (free function)

Best practices & limitations

  • Pagination: list_items does not follow @odata.nextLink. For very large lists, implement paging if needed.
  • Field names: prefer internal names. If you rely on display names/spaces/special chars, use encode_row.
  • Error handling: the code raises ValueError, TypeError, RuntimeError with Graph response details when available.
  • Rate limiting: Graph may return 429/503. Add retries/backoff for large batch operations.
  • Security: never commit client_secret. Use environment variables.

Example with environment variables:

import os
auth = GbAuth(
    tenant_id=os.environ["AZURE_TENANT_ID"],
    client_id=os.environ["AZURE_CLIENT_ID"],
    client_secret=os.environ["AZURE_CLIENT_SECRET"]
)

End-to-end (quick CRUD)

# CREATE
created = sp_list.create([
    {"Title": "Task A", "Status": "Active"},
    {"Title": "Task B", "Status": "Active"},
])
new_ids = [s["id"] for s in created["successes"]]

# READ
print(sp_list.list_rows)

# UPDATE
sp_list.update(ids=new_ids[0], rows={"Status": "Closed"})

# DELETE
sp_list.delete(new_ids[1])

Project layout (minimal)

If you don’t publish a package, paste the classes into gbsharepoint.py and:

# file: gbsharepoint.py
# (paste the provided classes/utilities here)

# then in your script:
from gbsharepoint import GbAuth, GbSite, GbList

License

Add the license that applies to your repository (e.g., MIT).


If you want, I can tailor examples to your actual site/list—just share hostname, site_path, and the list name.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

graphbridge-0.0.2.tar.gz (16.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

graphbridge-0.0.2-py3-none-any.whl (12.6 kB view details)

Uploaded Python 3

File details

Details for the file graphbridge-0.0.2.tar.gz.

File metadata

  • Download URL: graphbridge-0.0.2.tar.gz
  • Upload date:
  • Size: 16.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for graphbridge-0.0.2.tar.gz
Algorithm Hash digest
SHA256 ce91bde37c8f29b48c335ee918d60b320de6c91a6f1de9b19d2d72c4d074729e
MD5 aa13f0167ab4c67cee08114eb8c0f395
BLAKE2b-256 64657327e6f7c219f9375d1e2f41802dea2d7791f85042a09e7762b1f1132ed8

See more details on using hashes here.

File details

Details for the file graphbridge-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: graphbridge-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 12.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for graphbridge-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 d7ffc7731c5ee71f0ea1120251497b71bbd67048367ea3c04ba1b8a9fc9815a7
MD5 bec3972b4deb98e21362c1ce313f2259
BLAKE2b-256 e9ffdb2fcce984ddc545884c3a8127b54d6206b6fd2b0709d6177f8585cb4b8e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page