GraphBackend¶
Native async Microsoft Graph backend over OneDrive, SharePoint document
libraries, and Teams files. A single instance targets one drive
(drive_id); transport is httpx and auth is a token-provider callable
(the built-in GraphAuth helper, or any user-supplied callable). Requires
the graph extra. See the Graph setup guide
for provisioning credentials and resolving a drive_id.
Graph is async-only — there is no synchronous GraphBackend. To use it from
synchronous code, wrap it with AsyncBackendSyncAdapter.
GraphBackend
¶
GraphBackend(
drive_id: str,
*,
token_provider: TokenProvider,
base_url: str = _DEFAULT_BASE_URL,
http_client: AsyncClient | None = None,
retry: RetryPolicy | None = None,
upload_chunk_size: int = _DEFAULT_UPLOAD_CHUNK_SIZE,
copy_timeout: float | None = None,
base_path: str = "",
client_options: dict[str, Any] | None = None,
)
Async Microsoft Graph backend over OneDrive / SharePoint / Teams files.
A single instance targets one drive, identified by an immutable
drive_id. Items are addressed by /-rooted POSIX path; transport is
httpx; auth is a token-provider callable (the built-in GraphAuth
helper, or any user-supplied callable).
Parameters:
-
drive_id(str) –Opaque Graph drive id. Resolve one from a URL / "me" / Teams channel with
GraphUtils.resolve_drive_id. -
token_provider(TokenProvider) –Callable[[], str]orCallable[[], Awaitable[str]]returning a bearer token, invoked lazily (never in__init__). -
base_url(str, default:_DEFAULT_BASE_URL) –Graph API root (default
https://graph.microsoft.com/v1.0). -
http_client(AsyncClient | None, default:None) –Reuse an existing
httpx.AsyncClient; the caller owns its lifecycle andclose()does not close it. When omitted, one is created lazily on first use and closed byclose(). -
retry(RetryPolicy | None, default:None) –Retry policy for transient failures;
Noneuses the defaultRetryPolicy()profile. -
upload_chunk_size(int, default:_DEFAULT_UPLOAD_CHUNK_SIZE) –Upload-session chunk size; must be a positive multiple of 320 KiB and strictly less than 60 MiB (Graph's per-request ceiling). Default 10 MiB.
-
copy_timeout(float | None, default:None) –Wall-clock budget for copy/move monitor polling, or
Nonefor no backend-imposed ceiling. When set, must be a positive float. -
base_path(str, default:'') –Optional drive subfolder to scope every operation under. When set, all keys are addressed relative to this folder and keys returned by listing /
to_keystay relative to it, so the backend behaves as ifbase_pathwere its root. Defaults to the drive root. -
client_options(dict[str, Any] | None, default:None) –Extra options passed through to the internal
httpx.AsyncClient. (When a future revision adds an explicit httpx-level constructor parameter, it takes precedence over aclient_optionskey of the same name; the backend has no such parameter today, so this is passthrough only.)
Raises:
-
ValueError–For an empty
drive_id, a non-callabletoken_provider, a non-stringbase_path, anupload_chunk_sizethat is not a positive 320 KiB multiple below 60 MiB, or a non-positivecopy_timeout.
GraphAuth¶
MSAL-backed token provider for GraphBackend. Wraps the client-credentials
(app-only) and device-code (interactive) flows and exposes the bearer token
through the token-provider protocol — a GraphAuth instance is itself a
Callable[[], str].
GraphAuth
¶
GraphAuth(
tenant_id: str,
client_id: str,
*,
client_secret: str | Secret | None = None,
client_certificate: dict[str, Any] | None = None,
scopes: Sequence[str] | None = None,
cache_path: str | None = None,
prompt_callback: Callable[[dict[str, Any]], None]
| None = None,
)
MSAL-backed token provider for GraphBackend.
Selects the OAuth flow from the supplied credentials: a client_secret
or client_certificate selects client-credentials (app-only); their
absence selects device-code (interactive). The resulting bearer token is
reachable synchronously through get_token() (and through calling the
instance directly) and asynchronously through aget_token(), which
offloads the blocking MSAL work off the event loop and single-flights
concurrent acquisitions — prefer it on the event loop.
Parameters:
-
tenant_id(str) –Entra tenant id, or
"consumers"/"common"/"organizations"for the device-code multi-tenant authorities. -
client_id(str) –Application (client) id of the Entra app registration.
-
client_secret(str | Secret | None, default:None) –Client secret for client-credentials. Accepts a
Secretand is masked inrepr. -
client_certificate(dict[str, Any] | None, default:None) –Certificate dict for client-credentials, as MSAL's
client_credentialmapping. Mutually exclusive withclient_secret. -
scopes(Sequence[str] | None, default:None) –Override the default scope set. Client-credentials defaults to
["https://graph.microsoft.com/.default"]; device-code defaults to the delegated["Files.ReadWrite", "User.Read"](the signed-in user's OneDrive — consumer-compatible). AddSites.ReadWrite.Allfor delegated SharePoint access. -
cache_path(str | None, default:None) –Override the MSAL token-cache file location. Defaults to
<user_config_dir("remote-store")>/graph_token_cache.json. The cache is persisted multi-process-safely: a sibling<cache_path>.lockfilecoordinates concurrent writers so a shared default cache survives the common multi-worker deployment. -
prompt_callback(Callable[[dict[str, Any]], None] | None, default:None) –Invoked with the MSAL device-flow dict on device-code login; defaults to printing
flow["message"].
Raises:
-
ValueError–If
tenant_idorclient_idis empty, or if bothclient_secretandclient_certificateare supplied.
get_token
¶
Acquire (or silently refresh) a bearer token.
The token-provider callable the backend invokes. Re-invoking it after
a 401 refreshes through MSAL's cache. The acquisition writes the
refreshed cache through to disk itself — the backing
PersistedTokenCache persists under a cross-process lock on every
change, so no explicit flush is needed here.
Raises:
-
PermissionDenied–If MSAL returns no token (auth failure); the
error_descriptionis included, never the secret. A typedRemoteStoreErrorso a failure surfacing mid-read/writestays catchable viaexcept RemoteStoreError.
aget_token
async
¶
Acquire a bearer token without blocking the event loop.
The async token-provider entry point: pass token_provider=auth.aget_token
(a bound async method is a Callable[[], Awaitable[str]]) so the backend
awaits it instead of calling the synchronous instance. It offloads the
synchronous MSAL acquisition — including any contended token-cache lock
wait — to a worker thread so sibling coroutines keep running, and
single-flights concurrent callers: N coroutines acquiring at once share
one acquisition rather than each hitting the identity provider (which
also dedupes the one-shot refresh after a 401).
Use this on the event loop; reuse the synchronous get_token /
__call__ from synchronous wiring code that has no running loop. The
single-flight state is bound to the loop the first caller runs on — share
one instance per loop, mirroring the backend's own single-loop posture.
Cancelling one caller (e.g. a gather sibling that times out) neither
cancels the shared acquisition nor disturbs the other joiners.
Raises:
-
PermissionDenied–Propagated from
get_tokento the owning caller and every joiner sharing the in-flight acquisition; a later call retries afresh.
flush_cache
¶
Best-effort no-op retained for the GraphBackend.close() hook.
The token cache is a PersistedTokenCache that writes through to
disk under a cross-process lock on every acquisition, so there is
nothing to flush at close. The method stays because
GraphBackend.close() invokes it duck-typed and user-supplied
providers may implement their own. Never raises — teardown must not
fail.
GraphUtils¶
Namespace helpers for Graph configuration. resolve_drive_id turns "my
OneDrive" ("me"), a SharePoint site URL, or a Teams channel mapping into the
opaque drive_id the backend requires.
resolve_drive_id
staticmethod
¶
resolve_drive_id(
target: str | tuple[str, str] | Mapping[str, str],
*,
token_provider: TokenProvider,
http_client: AsyncClient | None = None,
base_url: str = _DEFAULT_BASE_URL,
) -> str
Resolve a drive_id from one of the three target shapes.
Sync entry point for application wiring; runs aresolve_drive_id
under a private event loop. See aresolve_drive_id for the accepted
shapes and raised errors.
aresolve_drive_id
async
staticmethod
¶
aresolve_drive_id(
target: str | tuple[str, str] | Mapping[str, str],
*,
token_provider: TokenProvider,
http_client: AsyncClient | None = None,
base_url: str = _DEFAULT_BASE_URL,
) -> str
Resolve a drive_id from one of three target shapes.
Accepted shapes:
"me"— the authenticated user's default drive (GET /me/drive).- a SharePoint site URL
str— the site's default drive; or a(site_url, library_name)tuple — the named document library. - a
{"team_id": ..., "channel_id": ...}mapping — a Teams channel's backing drive.
Parameters:
-
target(str | tuple[str, str] | Mapping[str, str]) –One of the shapes above.
-
token_provider(TokenProvider) –Bearer-token callable (sync or async).
-
http_client(AsyncClient | None, default:None) –Reuse an existing client; one is created and closed per call when omitted.
-
base_url(str, default:_DEFAULT_BASE_URL) –Graph API root.
Returns:
-
str–The opaque Graph
drive.idstring.
Raises:
-
InvalidPath–If
targetmatches no accepted shape, the SharePoint site URL has no host, or the named library does not exist. -
NotFound–If a site/team/channel id resolves but returns
404. -
PermissionDenied–If Graph returns
403for the lookup. -
BackendUnavailable–For a transport error, a retryable
5xx/429/507, or a malformed@odata.nextLinkwhile paging a site's document libraries.
See also¶
- Graph Backend Guide — usage patterns and capabilities
- Graph setup guide — provisioning credentials and resolving a
drive_id - Adapters — run
GraphBackendfrom synchronous code